summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2021-04-25 18:05:55 -0400
committerJason R. Coombs <jaraco@jaraco.com>2021-04-25 18:05:55 -0400
commitb720937d0af9e8382c8b9e00b2c0d5715b7cfbe5 (patch)
treec29ce9e7423c4f89e79634991941e5bdf9d07a15
parent8b494633df0f6770946092498aed76fd30be99b1 (diff)
parentb5fa6ad11e1344648b470ff0d847a7d940b4c99d (diff)
downloadpython-setuptools-git-feature/distutils-docs.tar.gz
Merge branch 'main' into feature/distutils-docsfeature/distutils-docs
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.coveragerc9
-rw-r--r--.editorconfig15
-rw-r--r--.flake825
-rw-r--r--.github/ISSUE_TEMPLATE/bug-report.yml129
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml15
-rw-r--r--.github/ISSUE_TEMPLATE/documentation-report.yml91
-rw-r--r--.github/ISSUE_TEMPLATE/feature-request.yml104
-rw-r--r--.github/workflows/automerge.yml27
-rw-r--r--.github/workflows/main.yml42
-rw-r--r--.github/workflows/python-tests.yml130
-rw-r--r--.pre-commit-config.yaml10
-rw-r--r--.readthedocs.yml10
-rw-r--r--.travis.yml61
-rw-r--r--CHANGES.rst672
-rw-r--r--LICENSE24
-rw-r--r--MANIFEST.in7
-rw-r--r--README.rst34
-rw-r--r--_distutils_hack/__init__.py5
-rw-r--r--appveyor.yml54
-rw-r--r--azure-pipelines.yml82
-rw-r--r--bootstrap.egg-info/PKG-INFO2
-rw-r--r--bootstrap.egg-info/entry_points.txt14
-rw-r--r--bootstrap.py56
-rw-r--r--changelog.d/2430.doc.1.rst2
-rw-r--r--changelog.d/2430.doc.2.rst2
-rw-r--r--changelog.d/2644.misc.rst1
-rw-r--r--changelog.d/2654.misc.rst2
-rw-r--r--docs/_templates/tidelift-sidebar.html6
-rw-r--r--docs/build_meta.rst13
-rw-r--r--docs/conf.py113
-rw-r--r--docs/pkg_resources.rst10
-rw-r--r--docs/references/keywords.rst11
-rw-r--r--docs/requirements.txt7
-rw-r--r--docs/setuptools.rst4
-rw-r--r--docs/userguide/declarative_config.rst15
-rw-r--r--docs/userguide/dependency_management.rst248
-rw-r--r--docs/userguide/development_mode.rst6
-rw-r--r--docs/userguide/entry_point.rst4
-rw-r--r--docs/userguide/package_discovery.rst114
-rw-r--r--docs/userguide/quickstart.rst55
-rw-r--r--easy_install.py5
-rw-r--r--mypy.ini2
-rw-r--r--netlify.toml7
-rw-r--r--pkg_resources/__init__.py14
-rw-r--r--pkg_resources/extern/__init__.py27
-rw-r--r--pkg_resources/tests/data/my-test-package-zip/my-test-package.zipbin0 -> 1809 bytes
-rw-r--r--pkg_resources/tests/test_find_distributions.py9
-rw-r--r--pkg_resources/tests/test_resources.py4
-rw-r--r--pyproject.toml28
-rw-r--r--pytest.ini11
-rw-r--r--runtime.txt1
-rw-r--r--setup.cfg142
-rwxr-xr-xsetup.py34
-rw-r--r--setuptools/archive_util.py94
-rw-r--r--setuptools/build_meta.py7
-rw-r--r--setuptools/command/__init__.py2
-rw-r--r--setuptools/command/bdist_egg.py51
-rw-r--r--setuptools/command/bdist_wininst.py30
-rw-r--r--setuptools/command/easy_install.py234
-rw-r--r--setuptools/command/egg_info.py129
-rw-r--r--setuptools/command/install_scripts.py3
-rw-r--r--setuptools/command/sdist.py55
-rw-r--r--setuptools/command/upload_docs.py8
-rw-r--r--setuptools/config.py17
-rw-r--r--setuptools/dist.py189
-rw-r--r--setuptools/extern/__init__.py27
-rw-r--r--setuptools/glob.py17
-rw-r--r--setuptools/installer.py71
-rw-r--r--setuptools/msvc.py44
-rw-r--r--setuptools/package_index.py76
-rw-r--r--setuptools/ssl_support.py2
-rw-r--r--setuptools/tests/files.py38
-rw-r--r--setuptools/tests/fixtures.py55
-rw-r--r--setuptools/tests/requirements.txt1
-rw-r--r--setuptools/tests/server.py2
-rw-r--r--setuptools/tests/test_bdist_deprecations.py23
-rw-r--r--setuptools/tests/test_bdist_egg.py15
-rw-r--r--setuptools/tests/test_build_ext.py7
-rw-r--r--setuptools/tests/test_build_meta.py45
-rw-r--r--setuptools/tests/test_config.py98
-rw-r--r--setuptools/tests/test_develop.py58
-rw-r--r--setuptools/tests/test_dist.py53
-rw-r--r--setuptools/tests/test_distutils_adoption.py6
-rw-r--r--setuptools/tests/test_easy_install.py45
-rw-r--r--setuptools/tests/test_egg_info.py104
-rw-r--r--setuptools/tests/test_glob.py5
-rw-r--r--setuptools/tests/test_integration.py65
-rw-r--r--setuptools/tests/test_manifest.py1
-rw-r--r--setuptools/tests/test_msvc.py6
-rw-r--r--setuptools/tests/test_namespaces.py5
-rw-r--r--setuptools/tests/test_sphinx_upload_docs.py38
-rw-r--r--setuptools/tests/test_upload_docs.py31
-rw-r--r--setuptools/tests/test_virtualenv.py76
-rw-r--r--setuptools/tests/test_wheel.py4
-rw-r--r--skeleton.md166
-rw-r--r--tools/tox_pip.py70
-rw-r--r--towncrier_template.rst4
-rw-r--r--tox.ini59
99 files changed, 2886 insertions, 1767 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 97840e42..dd76f43d 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 50.3.2
+current_version = 56.0.0
commit = True
tag = True
diff --git a/.coveragerc b/.coveragerc
index 2f0e8714..6a34e662 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,8 +1,7 @@
[run]
-source=
- pkg_resources
- setuptools
-omit=
- */_vendor/*
+omit =
+ # leading `*/` for pytest-dev/pytest-cov#456
+ */.tox/*
[report]
+show_missing = True
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..6385b573
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = tab
+indent_size = 4
+insert_final_newline = true
+end_of_line = lf
+
+[*.py]
+indent_style = space
+
+[*.{yml,yaml}]
+indent_style = space
+indent_size = 2
diff --git a/.flake8 b/.flake8
index c6580616..dd3cc206 100644
--- a/.flake8
+++ b/.flake8
@@ -1,12 +1,15 @@
[flake8]
-exclude=
- .tox
- setuptools/_vendor,
- pkg_resources/_vendor
-ignore =
- # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513
- W503
- # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545
- W504
- setuptools/site-patch.py F821
- setuptools/py*compat.py F811
+max-line-length = 88
+
+# jaraco/skeleton#34
+max-complexity = 10
+
+extend-exclude =
+ build
+ setuptools/_vendor
+ setuptools/_distutils
+ pkg_resources/_vendor
+
+extend-ignore =
+ # Black creates whitespace before colon
+ E203
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 00000000..73911ec8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,129 @@
+---
+name: 🐛 Bug report
+description: >-
+ Create a report to help us improve when
+ something is not working correctly
+title: '[BUG] '
+labels:
+- bug
+- Needs Triage
+
+body:
+- type: markdown
+ attributes:
+ value: >
+ **Thank you for wanting to report a bug in setuptools!**
+
+
+ ⚠
+ Verify first that your issue is not
+ [already reported on GitHub][issue search] and keep in mind and
+ keep in mind that we may have to keep the current behavior because
+ [every change breaks someone's workflow][XKCD 1172].
+ We try to be mindful about this.
+
+ Also test if the latest release and main branch are affected too.
+
+
+ If you are seeking community support, please consider
+ [starting a discussion][Discussions].
+
+
+ Thank you for your collaboration!
+
+
+ [Discussions]: https://github.com/pypa/setuptools/discussions
+
+ [issue search]: https://github.com/pypa/setuptools/search?q=is%3Aissue&type=issues
+
+ [XKCD 1172]: https://xkcd.com/1172/
+
+- type: markdown
+ attributes:
+ value: >-
+ **Environment**
+- type: input
+ attributes:
+ label: setuptools version
+ placeholder: For example, setuptools===60.4.2
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: Python version
+ placeholder: For example, Python 3.10
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: OS
+ placeholder: For example, Gentoo Linux, RHEL 8, Arch Linux, macOS etc.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Additional environment information
+ description: >-
+ Feel free to add more information about your environment here.
+ placeholder: >-
+ This is only happening when I run setuptools on my fridge's patched firmware 🤯
+
+- type: textarea
+ attributes:
+ label: Description
+ description: >-
+ A clear and concise description of what the bug is.
+ placeholder: >-
+ I tried doing X and I expected it to result in Y because the docs
+ mentioned Z but what happened next what totally unexpected!
+ And here's why...
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Expected behavior
+ description: >-
+ A clear and concise description of what you expected to happen.
+ placeholder: >-
+ I tried doing X and I expected it to result in Y. I'm confused...
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: How to Reproduce
+ description: >-
+ Describe the steps to reproduce this bug.
+ placeholder: |
+ 1. Integrate setuptools via '...'
+ 2. Then run '...'
+ 3. An error occurs.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Output
+ description: >-
+ Paste the output of the steps above, including the commands
+ themselves and setuptools' output/traceback etc.
+ value: |
+ ```console
+
+ ```
+ validations:
+ required: true
+
+
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [PSF Code of Conduct][CoC] first.
+
+ [CoC]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+ options:
+ - label: I agree to follow the PSF Code of Conduct
+ required: true
+...
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..dde102ca
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,15 @@
+# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
+blank_issues_enabled: false # default: true
+contact_links:
+- name: 🤔 Have questions or need support?
+ url: https://github.com/pypa/setuptools/discussions
+ about: This is a place for the community to exchange ideas and recipes
+- name: 💬 Discourse
+ url: https://discuss.python.org/c/packaging
+ about: |
+ Please ask typical Q&A here: general ideas for Python packaging,
+ questions about structuring projects and so on
+- name: >-
+ 💬 IRC: #pypa @ Freenode
+ url: https://webchat.freenode.net/#pypa
+ about: Chat with devs
diff --git a/.github/ISSUE_TEMPLATE/documentation-report.yml b/.github/ISSUE_TEMPLATE/documentation-report.yml
new file mode 100644
index 00000000..238ce896
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation-report.yml
@@ -0,0 +1,91 @@
+---
+name: 📝 Documentation Report
+title: '[Docs] '
+description: Ask us about docs
+labels:
+- documentation
+- Needs Triage
+
+body:
+- type: markdown
+ attributes:
+ value: >
+ **Thank you for wanting to report a problem with setuptools
+ documentation!**
+
+
+ Please fill out your suggestions below. If the problem seems
+ straightforward, feel free to go ahead and
+ submit a pull request instead!
+
+
+ ⚠
+ Verify first that your issue is not [already reported on
+ GitHub][issue search].
+
+
+ If you are seeking community support, please consider
+ [starting a discussion][Discussions].
+
+
+ Thank you for your collaboration!
+
+
+ [issue search]: https://github.com/pypa/setuptools/search?q=is%3Aissue&type=issues
+
+ [Discussions]: https://github.com/pypa/setuptools/discussions
+
+- type: textarea
+ attributes:
+ label: Summary
+ description: >
+ Explain the problem briefly below, add suggestions to wording
+ or structure.
+
+
+ **HINT:** Did you know the documentation has a `View on GitHub`
+ link on every page? Feel free to use it to start a pull request
+ right from the GitHub UI!
+ placeholder: >-
+ I was reading the setuptools documentation of version X and I'm
+ having problems understanding Y. It would be very helpful if that
+ got rephrased as Z.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: OS / Environment
+ description: >-
+ Provide all relevant information below, e.g. OS version,
+ browser, etc.
+ placeholder: Fedora 33, Firefox etc.
+
+
+- type: textarea
+ attributes:
+ label: Additional Information
+ description: >
+ Describe how this improves the documentation, e.g. before/after
+ situation or screenshots.
+
+
+ **HINT:** You can paste https://gist.github.com links for larger files.
+ placeholder: >-
+ When the improvement is applied, it makes it more straightforward
+ to understand X.
+ validations:
+ required: true
+
+
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [PSF Code of Conduct][CoC] first.
+
+ [CoC]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+ options:
+ - label: I agree to follow the PSF Code of Conduct
+ required: true
+...
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
new file mode 100644
index 00000000..88ae6741
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -0,0 +1,104 @@
+---
+name: ✨ Feature request
+description: Suggest an idea for setuptools
+title: '[FR] '
+labels:
+- enhancement
+- Needs Triage
+
+body:
+- type: markdown
+ attributes:
+ value: >
+ **Thank you for wanting to suggest a feature for setuptools!**
+
+
+ 💡
+ Before you go ahead with your request, please first consider if it
+ would be useful for majority of the setuptools users. As a general
+ rule of thumb, any feature that is only of interest to a small sub
+ group should be implemented in a third-party plugin or maybe even
+ just your project alone. Be mindful of the fact that the core
+ setuptools features have a broad impact.
+
+
+ <details>
+ <summary>
+ ❗ Every change breaks someone's workflow.
+ </summary>
+
+
+ [![❗ Every change breaks someone's workflow.](https://imgs.xkcd.com/comics/workflow.png)
+ ](https://xkcd.com/1172/)
+ </details>
+
+
+ ⚠
+ Verify first that your idea is not [already requested on GitHub][issue search].
+
+
+
+ [issue search]: https://github.com/pypa/setuptools/search?q=is%3Aissue&type=issues
+
+- type: textarea
+ attributes:
+ label: What's the problem this feature will solve?
+ description: >-
+ What are you trying to do, that you are unable to achieve
+ with setuptools as it currently stands?
+ placeholder: >-
+ I'm trying to do X and I'm missing feature Y for this to be
+ easily achievable.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Describe the solution you'd like
+ description: >
+ Clear and concise description of what you want to happen.
+
+
+ Provide examples of real world use cases that this would enable
+ and how it solves the problem described above.
+ placeholder: >-
+ When I do X, I want to achieve Y in a situation when Z.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Alternative Solutions
+ description: >-
+ Have you tried to workaround the problem using other tools? Or a
+ different approach to solving this issue? Please elaborate here.
+ placeholder: >-
+ I tried doing X, Y and Z. But they are subobpimal because of P.
+
+- type: textarea
+ attributes:
+ label: Additional context
+ description: >
+ Add any other context, links, etc. about the feature here.
+ Describe how the feature would be used, why it is needed and what
+ it would solve.
+
+
+ **HINT:** You can paste https://gist.github.com links for
+ larger files.
+ placeholder: >-
+ I asked on https://stackoverflow.com/.... and the community
+ advised me to do X, Y and Z.
+
+
+- type: checkboxes
+ attributes:
+ label: Code of Conduct
+ description: |
+ Read the [PSF Code of Conduct][CoC] first.
+
+ [CoC]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
+ options:
+ - label: I agree to follow the PSF Code of Conduct
+ required: true
+...
diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml
new file mode 100644
index 00000000..4f70acfb
--- /dev/null
+++ b/.github/workflows/automerge.yml
@@ -0,0 +1,27 @@
+name: automerge
+on:
+ pull_request:
+ types:
+ - labeled
+ - unlabeled
+ - synchronize
+ - opened
+ - edited
+ - ready_for_review
+ - reopened
+ - unlocked
+ pull_request_review:
+ types:
+ - submitted
+ check_suite:
+ types:
+ - completed
+ status: {}
+jobs:
+ automerge:
+ runs-on: ubuntu-latest
+ steps:
+ - name: automerge
+ uses: "pascalgn/automerge-action@v0.12.0"
+ env:
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..37d65f33
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,42 @@
+name: tests
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ strategy:
+ matrix:
+ python: [3.6, 3.8, 3.9, pypy3]
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.platform }}
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python }}
+ - name: Install tox
+ run: |
+ python -m pip install tox
+ - name: Run tests
+ run: tox
+
+ release:
+ needs: test
+ if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+ - name: Install tox
+ run: |
+ python -m pip install tox
+ - name: Release
+ run: tox -e release
+ env:
+ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
deleted file mode 100644
index 2ee7c0fb..00000000
--- a/.github/workflows/python-tests.yml
+++ /dev/null
@@ -1,130 +0,0 @@
-name: >-
- 👷
- Test suite
-
-on:
- push:
- pull_request:
- schedule:
- - cron: 1 0 * * * # Run daily at 0:01 UTC
-
-jobs:
- tests:
- name: >-
- ${{ matrix.python-version }}
- /
- ${{ matrix.os }}
- runs-on: ${{ matrix.os }}
- strategy:
- # max-parallel: 5
- matrix:
- python-version:
- - 3.9
- - 3.8
- - pypy3
- - 3.7
- - 3.6
- - 3.5
- os:
- - ubuntu-18.04
- - ubuntu-16.04
- - macOS-latest
- # - windows-2019
- # - windows-2016
- include:
- # Dev versions (deadsnakes)
- - os: ubuntu-20.04
- python-version: 3.9-dev
- - os: ubuntu-20.04
- python-version: 3.8-dev
-
- env:
- NETWORK_REQUIRED: 1
- PYTHON_VERSION: ${{ matrix.python-version }}
- TOX_PARALLEL_NO_SPINNER: 1
- TOXENV: python
- USE_DEADSNAKES: false
-
- steps:
- - uses: actions/checkout@master
- - name: Set flag to use deadsnakes
- if: >-
- endsWith(env.PYTHON_VERSION, '-beta') ||
- endsWith(env.PYTHON_VERSION, '-dev')
- run: |
- from __future__ import print_function
- python_version = '${{ env.PYTHON_VERSION }}'.replace('-beta', '')
- print('::set-env name=PYTHON_VERSION::{ver}'.format(ver=python_version))
- print('::set-env name=USE_DEADSNAKES::true')
- shell: python
- - name: Set up Python ${{ env.PYTHON_VERSION }} (deadsnakes)
- uses: deadsnakes/action@v1.0.0
- if: fromJSON(env.USE_DEADSNAKES) && true || false
- with:
- python-version: ${{ env.PYTHON_VERSION }}
- - name: Set up Python ${{ env.PYTHON_VERSION }}
- uses: actions/setup-python@v2.1.1
- if: >-
- !fromJSON(env.USE_DEADSNAKES) && true || false
- with:
- python-version: ${{ env.PYTHON_VERSION }}
- - name: Log Python version
- run: >-
- python --version
- - name: Log Python location
- run: >-
- which python
- - name: Log Python env
- run: >-
- python -m sysconfig
- - name: Pip cache
- uses: actions/cache@v1
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
- restore-keys: |
- ${{ runner.os }}-pip-
- ${{ runner.os }}-
- - name: Upgrade pip/setuptools/wheel
- run: >-
- python
- -m pip install
- --disable-pip-version-check
- --upgrade
- pip setuptools wheel
- - name: Install tox
- run: >-
- python -m pip install --upgrade tox tox-venv
- - name: Log installed dists
- run: >-
- python -m pip freeze --all
- - name: Adjust TOXENV for PyPy
- if: startsWith(env.PYTHON_VERSION, 'pypy')
- run: >-
- echo "::set-env name=TOXENV::${{ env.PYTHON_VERSION }}"
- - name: Log env vars
- run: >-
- env
-
- - name: Verify that there's no cached Python modules in sources
- if: >-
- ! startsWith(matrix.os, 'windows-')
- run: >-
- ! grep pyc setuptools.egg-info/SOURCES.txt
-
- - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}'
- run: >-
- python -m
- tox
- --parallel auto
- --parallel-live
- --notest
- --skip-missing-interpreters false
- - name: Test with tox
- run: >-
- python -m
- tox
- --parallel auto
- --parallel-live
- --
- -vvvvv
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..c15ab0c9
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,10 @@
+repos:
+- repo: https://github.com/psf/black
+ rev: 20.8b1
+ hooks:
+ - id: black
+
+- repo: https://github.com/asottile/blacken-docs
+ rev: v1.9.1
+ hooks:
+ - id: blacken-docs
diff --git a/.readthedocs.yml b/.readthedocs.yml
index cb10a7f9..cc698548 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -1,6 +1,6 @@
+version: 2
python:
- version: 3
- extra_requirements:
- - docs
- pip_install: false
- requirements: docs/requirements.txt
+ install:
+ - path: .
+ extra_requirements:
+ - docs
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7d8c1026..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-dist: xenial
-language: python
-
-jobs:
- fast_finish: true
- include:
- - python: pypy3
- - python: 3.5
- - python: 3.6
- - python: 3.7
- - &latest_py3
- python: 3.8
- - <<: *latest_py3
- env: LANG=C
- - python: 3.8-dev
- - python: 3.9-dev
- - <<: *latest_py3
- env: TOXENV=docs
- - arch: ppc64le
- python: pypy3
- - arch: ppc64le
- python: 3.5
- - &latest_py3_ppc
- arch: ppc64le
- python: 3.8
- - <<: *latest_py3_ppc
- env: LANG=C
- - arch: ppc64le
- python: 3.9-dev
- allow_failures:
- # suppress failures due to pypa/setuptools#2000
- - python: pypy3
- - <<: *latest_py3
- env: TOXENV=docs
-
-
-cache: pip
-
-before_install:
-- python tools/ppc64le-patch.py
-
-install:
-
-# ensure we have recent pip/setuptools/wheel
-- pip install --disable-pip-version-check --upgrade pip setuptools wheel
-# need tox to get started
-- pip install --upgrade tox tox-venv
-
-# Output the env, to verify behavior
-- pip freeze --all
-- env
-
-- "! grep pyc setuptools.egg-info/SOURCES.txt"
-
-script:
- - export NETWORK_REQUIRED=1
- - tox
-
-after_success:
- - export TRAVIS_JOB_NAME="${TRAVIS_PYTHON_VERSION} (LANG=$LANG)" CODECOV_ENV=TRAVIS_JOB_NAME
- - tox -e coverage,codecov
diff --git a/CHANGES.rst b/CHANGES.rst
index 30750c0a..ef1d926b 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,262 @@
+v56.0.0
+-------
+
+
+Deprecations
+^^^^^^^^^^^^
+* #2620: The ``license_file`` option is now marked as deprecated.
+ Use ``license_files`` instead. -- by :user:`cdce8p`
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2620: If neither ``license_file`` nor ``license_files`` is specified, the ``sdist``
+ option will now auto-include files that match the following patterns:
+ ``LICEN[CS]E*``, ``COPYING*``, ``NOTICE*``, ``AUTHORS*``.
+ This matches the behavior of ``bdist_wheel``. -- by :user:`cdce8p`
+
+Changes
+^^^^^^^
+* #2620: The ``license_file`` and ``license_files`` options now support glob patterns. -- by :user:`cdce8p`
+* #2632: Implemented ``VendorImporter.find_spec()`` method to get rid
+ of ``ImportWarning`` that Python 3.10 emits when only the old-style
+ importer hooks are present -- by :user:`webknjaz`
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #2620: Added documentation for the ``license_files`` option. -- by :user:`cdce8p`
+
+
+v55.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2566: Remove the deprecated ``bdist_wininst`` command. Binary packages should be built as wheels instead. -- by :user:`hroncok`
+
+
+v54.2.0
+-------
+
+
+Changes
+^^^^^^^
+* #2608: Added informative error message to PEP 517 build failures owing to
+ an empty ``setup.py`` -- by :user:`layday`
+
+
+v54.1.3
+-------
+
+No significant changes.
+
+
+v54.1.2
+-------
+
+
+Misc
+^^^^
+* #2595: Reduced scope of dash deprecation warning to Setuptools/distutils only -- by :user:`melissa-kun-li`
+
+
+v54.1.1
+-------
+
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #2584: Added ``sphinx-inline-tabs`` extension to allow for comparison of ``setup.py`` and its equivalent ``setup.cfg`` -- by :user:`amy-lei`
+
+Misc
+^^^^
+* #2592: Made option keys in the ``[metadata]`` section of ``setup.cfg`` case-sensitive. Users having
+ uppercase option spellings will get a warning suggesting to make them to lowercase
+ -- by :user:`melissa-kun-li`
+
+
+v54.1.0
+-------
+
+
+Changes
+^^^^^^^
+* #1608: Removed the conversion of dashes to underscores in the :code:`extras_require` and :code:`data_files` of :code:`setup.cfg` to support the usage of dashes. Method will warn users when they use a dash-separated key which in the future will only allow an underscore. Note: the method performs the dash to underscore conversion to preserve compatibility, but future versions will no longer support it -- by :user:`melissa-kun-li`
+
+
+v54.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2582: Simplified build-from-source story by providing bootstrapping metadata in a separate egg-info directory. Build requirements no longer include setuptools itself. Sdist once again includes the pyproject.toml. Project can no longer be installed from source on pip 19.x, but install from source is still supported on pip < 19 and pip >= 20 and install from wheel is still supported with pip >= 9.
+
+Changes
+^^^^^^^
+* #1932: Handled :code:`AttributeError` by raising :code:`DistutilsSetupError` in :code:`dist.check_specifier()` when specifier is not a string -- by :user:`melissa-kun-li`
+* #2570: Correctly parse cmdclass in setup.cfg.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #2553: Added userguide example for markers in extras_require -- by :user:`pwoolvett`
+
+
+v53.1.0
+-------
+
+
+Changes
+^^^^^^^
+* #1937: Preserved case-sensitivity of keys in setup.cfg so that entry point names are case-sensitive. Changed sensitivity of configparser. NOTE: Any projects relying on case-insensitivity will need to adapt to accept the original case as published. -- by :user:`melissa-kun-li`
+* #2573: Fixed error in uploading a Sphinx doc with the :code:`upload_docs` command. An html builder will be used.
+ Note: :code:`upload_docs` is deprecated for PyPi, but is supported for other sites -- by :user:`melissa-kun-li`
+
+
+v53.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #1527: Removed bootstrap script. Now Setuptools requires pip or another pep517-compliant builder such as 'build' to build. Now Setuptools can be installed from Github main branch.
+
+
+v52.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2537: Remove fallback support for fetch_build_eggs using easy_install. Now pip is required for setup_requires to succeed.
+* #2544: Removed 'easy_install' top-level model (runpy entry point) and 'easy_install' console script.
+* #2545: Removed support for eggsecutables.
+
+Changes
+^^^^^^^
+* #2459: Tests now run in parallel via pytest-xdist, completing in about half the time. Special thanks to :user:`webknjaz` for hard work implementing test isolation. To run without parallelization, disable the plugin with ``tox -- -p no:xdist``.
+
+
+v51.3.3
+-------
+
+
+Misc
+^^^^
+* #2539: Fix AttributeError in Description validation.
+
+
+v51.3.2
+-------
+
+
+Misc
+^^^^
+* #1390: Validation of Description field now is more lenient, emitting a warning and mangling the value to be valid (replacing newlines with spaces).
+
+
+v51.3.1
+-------
+
+
+Misc
+^^^^
+* #2536: Reverted tag deduplication handling.
+
+
+v51.3.0
+-------
+
+
+Changes
+^^^^^^^
+* #1390: Newlines in metadata description/Summary now trigger a ValueError.
+* #2481: Define ``create_module()`` and ``exec_module()`` methods in ``VendorImporter``
+ to get rid of ``ImportWarning`` -- by :user:`hroncok`
+* #2489: ``pkg_resources`` behavior for zipimport now matches the regular behavior, and finds
+ ``.egg-info`` (previoulsy would only find ``.dist-info``) -- by :user:`thatch`
+* #2529: Fixed an issue where version tags may be added multiple times
+
+
+v51.2.0
+-------
+
+
+Changes
+^^^^^^^
+* #2493: Use importlib.import_module() rather than the deprectated loader.load_module()
+ in pkg_resources namespace delaration -- by :user:`encukou`
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #2525: Fix typo in the document page about entry point. -- by :user:`jtr109`
+
+Misc
+^^^^
+* #2534: Avoid hitting network during test_easy_install.
+
+
+v51.1.2
+-------
+
+
+Misc
+^^^^
+* #2505: Disable inclusion of package data as it causes 'tests' to be included as data.
+
+
+v51.1.1
+-------
+
+
+Misc
+^^^^
+* #2534: Avoid hitting network during test_virtualenv.test_test_command.
+
+
+v51.1.0
+-------
+
+
+Changes
+^^^^^^^
+* #2486: Project adopts jaraco/skeleton for shared package maintenance.
+
+Misc
+^^^^
+* #2477: Restore inclusion of rst files in sdist.
+* #2484: Setuptools has replaced the master branch with the main branch.
+* #2485: Fixed failing test when pip 20.3+ is present.
+ -- by :user:`yan12125`
+* #2487: Fix tests with pytest 6.2
+ -- by :user:`yan12125`
+
+
+v51.0.0
+-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
+* #2435: Require Python 3.6 or later.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #2430: Fixed inconsistent RST title nesting levels caused by #2399
+ -- by :user:`webknjaz`
+* #2430: Fixed a typo in Sphinx docs that made docs dev section disappear
+ as a result of PR #2426 -- by :user:`webknjaz`
+
+Misc
+^^^^
+* #2471: Removed the tests that guarantee that the vendored dependencies can be built by distutils.
+
+
v50.3.2
-------
+
Documentation changes
^^^^^^^^^^^^^^^^^^^^^
* #2394: Extended towncrier news template to include change note categories.
@@ -23,12 +278,19 @@ Misc
v50.3.1
-------
+
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #2093: Finalized doc revamp.
* #2097: doc: simplify index and group deprecated files
* #2102: doc overhaul step 2: break main doc into multiple sections
* #2111: doc overhaul step 3: update userguide
* #2395: Added a ``:user:`` role to Sphinx config -- by :user:`webknjaz`
* #2395: Added an illustrative explanation about the change notes to fragments dir -- by :user:`webknjaz`
+
+Misc
+^^^^
* #2379: Travis CI test suite now tests against PPC64.
* #2413: Suppress EOF errors (and other exceptions) when importing lib2to3.
@@ -36,12 +298,20 @@ v50.3.1
v50.3.0
-------
+
+
+Changes
+^^^^^^^
* #2368: In distutils, restore support for monkeypatched CCompiler.spawn per pypa/distutils#15.
v50.2.0
-------
+
+
+Changes
+^^^^^^^
* #2355: When pip is imported as part of a build, leave distutils patched.
* #2380: There are some setuptools specific changes in the
``setuptools.command.bdist_rpm`` module that are no longer needed, because
@@ -52,24 +322,40 @@ v50.2.0
v50.1.0
-------
+
+
+Changes
+^^^^^^^
* #2350: Setuptools reverts using the included distutils by default. Platform maintainers and system integrators and others are *strongly* encouraged to set ``SETUPTOOLS_USE_DISTUTILS=local`` to help identify and work through the reported issues with distutils adoption, mainly to file issues and pull requests with pypa/distutils such that distutils performs as needed across every supported environment.
v50.0.3
-------
+
+
+Misc
+^^^^
* #2363: Restore link_libpython support on Python 3.7 and earlier (see pypa/distutils#9).
v50.0.2
-------
+
+
+Misc
+^^^^
* #2352: In distutils hack, use absolute import rather than relative to avoid bpo-30876.
v50.0.1
-------
+
+
+Misc
+^^^^
* #2357: Restored Python 3.5 support in distutils.util for missing ``subprocess._optim_args_from_interpreter_flags``.
* #2358: Restored AIX support on Python 3.8 and earlier.
* #2361: Add Python 3.10 support to _distutils_hack. Get the 'Loader' abstract class
@@ -80,19 +366,34 @@ v50.0.1
v50.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #2232: Once again, Setuptools overrides the stdlib distutils on import. For environments or invocations where this behavior is undesirable, users are provided with a temporary escape hatch. If the environment variable ``SETUPTOOLS_USE_DISTUTILS`` is set to ``stdlib``, Setuptools will fall back to the legacy behavior. Use of this escape hatch is discouraged, but it is provided to ease the transition while proper fixes for edge cases can be addressed.
+
+Changes
+^^^^^^^
* #2334: In MSVC module, refine text in error message.
v49.6.0
-------
+
+
+Changes
+^^^^^^^
* #2129: In pkg_resources, no longer detect any pathname ending in .egg as a Python egg. Now the path must be an unpacked egg or a zip file.
v49.5.0
-------
+
+
+Changes
+^^^^^^^
* #2306: When running as a PEP 517 backend, setuptools does not try to install
``setup_requires`` itself. They are reported as build requirements for the
frontend to install.
@@ -101,43 +402,74 @@ v49.5.0
v49.4.0
-------
+
+
+Changes
+^^^^^^^
* #2310: Updated vendored packaging version to 20.4.
v49.3.2
-------
+
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #2300: Improve the ``safe_version`` function documentation
+
+Misc
+^^^^
* #2297: Once again, in stubs prefer exec_module to the deprecated load_module.
v49.3.1
-------
+
+
+Changes
+^^^^^^^
* #2316: Removed warning when ``distutils`` is imported before ``setuptools`` when ``distutils`` replacement is not enabled.
v49.3.0
-------
+
+
+Changes
+^^^^^^^
* #2259: Setuptools now provides a .pth file (except for editable installs of setuptools) to the target environment to ensure that when enabled, the setuptools-provided distutils is preferred before setuptools has been imported (and even if setuptools is never imported). Honors the SETUPTOOLS_USE_DISTUTILS environment variable.
v49.2.1
-------
+
+
+Misc
+^^^^
* #2257: Fixed two flaws in distutils._msvccompiler.MSVCCompiler.spawn.
v49.2.0
-------
+
+
+Changes
+^^^^^^^
* #2230: Now warn the user when setuptools is imported after distutils modules have been loaded (exempting PyPy for 3.6), directing the users of packages to import setuptools first.
v49.1.3
-------
+
+
+Misc
+^^^^
* #2212: (Distutils) Allow spawn to accept environment. Avoid monkey-patching global state.
* #2249: Fix extension loading technique in stubs.
@@ -145,39 +477,69 @@ v49.1.3
v49.1.2
-------
+
+
+Changes
+^^^^^^^
* #2232: In preparation for re-enabling a local copy of distutils, Setuptools now honors an environment variable, SETUPTOOLS_USE_DISTUTILS. If set to 'stdlib' (current default), distutils will be used from the standard library. If set to 'local' (default in a imminent backward-incompatible release), the local copy of distutils will be used.
v49.1.1
-------
+
+
+Misc
+^^^^
* #2094: Removed pkg_resources.py2_warn module, which is no longer reachable.
v49.0.1
-------
+
+
+Misc
+^^^^
* #2228: Applied fix for pypa/distutils#3, restoring expectation that spawn will raise a DistutilsExecError when attempting to execute a missing file.
v49.1.0
-------
+
+
+Changes
+^^^^^^^
* #2228: Disabled distutils adoption for now while emergent issues are addressed.
v49.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #2165: Setuptools no longer installs a site.py file during easy_install or develop installs. As a result, .eggs on PYTHONPATH will no longer take precedence over other packages on sys.path. If this issue affects your production environment, please reach out to the maintainers at #2165.
+
+Changes
+^^^^^^^
* #2137: Removed (private) pkg_resources.RequirementParseError, now replaced by packaging.requirements.InvalidRequirement. Kept the name for compatibility, but users should catch InvalidRequirement instead.
* #2180: Update vendored packaging in pkg_resources to 19.2.
+
+Misc
+^^^^
* #2199: Fix exception causes all over the codebase by using ``raise new_exception from old_exception``
v48.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #2143: Setuptools adopts distutils from the Python 3.9 standard library and no longer depends on distutils in the standard library. When importing ``setuptools`` or ``setuptools.distutils_patch``, Setuptools will expose its bundled version as a top-level ``distutils`` package (and unload any previously-imported top-level distutils package), retaining the expectation that ``distutils``' objects are actually Setuptools objects.
To avoid getting any legacy behavior from the standard library, projects are advised to always "import setuptools" prior to importing anything from distutils. This behavior happens by default when using ``pip install`` or ``pep517.build``. Workflows that rely on ``setup.py (anything)`` will need to first ensure setuptools is imported. One way to achieve this behavior without modifying code is to invoke Python thus: ``python -c "import setuptools; exec(open('setup.py').read())" (anything)``.
@@ -185,12 +547,20 @@ v48.0.0
v47.3.2
-------
+
+
+Misc
+^^^^
* #2071: Replaced references to the deprecated imp package with references to importlib
v47.3.1
-------
+
+
+Misc
+^^^^
* #1973: Removed ``pkg_resources.py31compat.makedirs`` in favor of the stdlib. Use ``os.makedirs()`` instead.
* #2198: Restore ``__requires__`` directive in easy-install wrapper scripts.
@@ -198,22 +568,39 @@ v47.3.1
v47.3.0
-------
+
+
+Changes
+^^^^^^^
* #2197: Console script wrapper for editable installs now has a unified template and honors importlib_metadata if present for faster script execution on older Pythons.
+
+Misc
+^^^^
* #2195: Fix broken entry points generated by easy-install (pip editable installs).
v47.2.0
-------
+
+
+Changes
+^^^^^^^
* #2194: Editable-installed entry points now load significantly faster on Python versions 3.8+.
+* #1471: Incidentally fixed by #2194 on Python 3.8 or when importlib_metadata is present.
v47.1.1
-------
+
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #2156: Update mailing list pointer in developer docs
Incorporate changes from v44.1.1:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* #2158: Avoid loading working set during ``Distribution.finalize_options`` prior to invoking ``_install_setup_requires``, broken since v42.0.0.
@@ -221,25 +608,44 @@ Incorporate changes from v44.1.1:
v44.1.1
-------
+
+
+Misc
+^^^^
* #2158: Avoid loading working set during ``Distribution.finalize_options`` prior to invoking ``_install_setup_requires``, broken since v42.0.0.
v47.1.0
-------
+
+
+Changes
+^^^^^^^
* #2070: In wheel-to-egg conversion, use simple pkg_resources-style namespace declaration for packages that declare namespace_packages.
v47.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #2094: Setuptools now actively crashes under Python 2. Python 3.5 or later is required. Users of Python 2 should use ``setuptools<45``.
+
+Changes
+^^^^^^^
* #1700: Document all supported keywords by migrating the ones from distutils.
v46.4.0
-------
+
+
+Changes
+^^^^^^^
* #1753: ``attr:`` now extracts variables through rudimentary examination of the AST,
thereby supporting modules with third-party imports. If examining the AST
fails to find the variable, ``attr:`` falls back to the old behavior of
@@ -255,7 +661,14 @@ No significant changes.
v46.3.0
-------
+
+
+Changes
+^^^^^^^
* #2089: Package index functionality no longer attempts to remove an md5 fragment from the index URL. This functionality, added for distribute #163 is no longer relevant.
+
+Misc
+^^^^
* #2041: Preserve file modes during pkg files copying, but clear read only flag for target afterwards.
* #2105: Filter ``2to3`` deprecation warnings from ``TestDevelop.test_2to3_user_mode``.
@@ -263,11 +676,21 @@ v46.3.0
v46.2.0
-------
+
+
+Changes
+^^^^^^^
* #2040: Deprecated the ``bdist_wininst`` command. Binary packages should be built as wheels instead.
* #2062: Change 'Mac OS X' to 'macOS' in code.
* #2075: Stop recognizing files ending with ``.dist-info`` as distribution metadata.
* #2086: Deprecate 'use_2to3' functionality. Packagers are encouraged to use single-source solutions or build tool chains to manage conversions outside of setuptools.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1698: Added documentation for ``build_meta`` (a bare minimum, not completed).
+
+Misc
+^^^^
* #2082: Filter ``lib2to3`` ``PendingDeprecationWarning`` and ``DeprecationWarning`` in tests,
because ``lib2to3`` is `deprecated in Python 3.9 <https://bugs.python.org/issue40360>`_.
@@ -281,6 +704,10 @@ No significant changes.
v46.1.2
-------
+
+
+Misc
+^^^^
* #1458: Added template for reporting Python 2 incompatibilities.
@@ -293,12 +720,17 @@ No significant changes.
v46.1.0
-------
+
+
+Changes
+^^^^^^^
* #308: Allow version number normalization to be bypassed by wrapping in a 'setuptools.sic()' call.
* #1424: Prevent keeping files mode for package_data build. It may break a build if user's package data has read only flag.
* #1431: In ``easy_install.check_site_dir``, ensure the installation directory exists.
* #1563: In ``pkg_resources`` prefer ``find_spec`` (PEP 451) to ``find_module``.
Incorporate changes from v44.1.0:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* #1704: Set sys.argv[0] in setup script run by build_meta.__legacy__
* #1959: Fix for Python 4: replace unsafe six.PY3 with six.PY2
@@ -308,6 +740,10 @@ Incorporate changes from v44.1.0:
v44.1.0
-------
+
+
+Changes
+^^^^^^^
* #1704: Set sys.argv[0] in setup script run by build_meta.__legacy__
* #1959: Fix for Python 4: replace unsafe six.PY3 with six.PY2
* #1994: Fixed a bug in the "setuptools.finalize_distribution_options" hook that lead to ignoring the order attribute of entry points managed by this hook.
@@ -316,16 +752,33 @@ v44.1.0
v46.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #65: Once again as in 3.0, removed the Features feature.
+
+Changes
+^^^^^^^
* #1890: Fix vendored dependencies so importing ``setuptools.extern.some_module`` gives the same object as ``setuptools._vendor.some_module``. This makes Metadata picklable again.
* #1899: Test suite now fails on warnings.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #2011: Fix broken link to distutils docs on package_data
+
+Misc
+^^^^
* #1991: Include pkg_resources test data in sdist, so tests can be executed from it.
v45.3.0
-------
+
+
+Changes
+^^^^^^^
* #1557: Deprecated eggsecutable scripts and updated docs.
* #1904: Update msvc.py to use CPython 3.8.0 mechanism to find msvc 14+
@@ -333,6 +786,10 @@ v45.3.0
v45.2.0
-------
+
+
+Changes
+^^^^^^^
* #1905: Fixed defect in _imp, introduced in 41.6.0 when the 'tests' directory is not present.
* #1941: Improve editable installs with PEP 518 build isolation:
@@ -340,12 +797,19 @@ v45.2.0
* The error shown when the install directory is not in ``PYTHONPATH`` has been turned into a warning.
* #1981: Setuptools now declares its ``tests`` and ``docs`` dependencies in metadata (extras).
* #1985: Add support for installing scripts in environments where bdist_wininst is missing (i.e. Python 3.9).
+
+Misc
+^^^^
* #1968: Add flake8-2020 to check for misuse of sys.version or sys.version_info.
v45.1.0
-------
+
+
+Changes
+^^^^^^^
* #1458: Add minimum sunset date and preamble to Python 2 warning.
* #1704: Set sys.argv[0] in setup script run by build_meta.__legacy__
* #1974: Add Python 3 Only Trove Classifier and remove universal wheel declaration for more complete transition from Python 2.
@@ -354,26 +818,47 @@ v45.1.0
v45.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #1458: Drop support for Python 2. Setuptools now requires Python 3.5 or later. Install setuptools using pip >=9 or pin to Setuptools <45 to maintain 2.7 support.
+
+Changes
+^^^^^^^
* #1959: Fix for Python 4: replace unsafe six.PY3 with six.PY2
v44.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #1908: Drop support for Python 3.4.
v43.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #1634: Include ``pyproject.toml`` in source distribution by default. Projects relying on the previous behavior where ``pyproject.toml`` was excluded by default should stop relying on that behavior or add ``exclude pyproject.toml`` to their MANIFEST.in file.
+
+Changes
+^^^^^^^
* #1927: 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.
v42.0.2
-------
+Changes
+^^^^^^^
+
* #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.
@@ -381,12 +866,20 @@ v42.0.2
v42.0.1
-------
+
+
+Changes
+^^^^^^^
* #1918: Fix regression in handling wheels compatibility tags.
v42.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #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)
@@ -395,6 +888,9 @@ v42.0.0
* 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>`_.
+
+Changes
+^^^^^^^
* #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:
@@ -407,46 +903,82 @@ v42.0.0
v41.6.0
-------
+
+
+Changes
+^^^^^^^
* #479: Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``.
v41.5.1
-------
+
+
+Changes
+^^^^^^^
* #1891: Fix code for detecting Visual Studio's version on Windows under Python 2.
v41.5.0
-------
+
+
+Changes
+^^^^^^^
* #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``.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #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.
+
+Misc
+^^^^
* #1886: Added Python 3.8 release to the Travis test matrix.
v41.4.0
-------
+
+
+Changes
+^^^^^^^
* #1847: In declarative config, now traps errors when invalid ``python_requires`` values are supplied.
v41.3.0
-------
+
+
+Changes
+^^^^^^^
* #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+.
+
+Misc
+^^^^
* #1858: Fixed failing integration test triggered by 'long_description_content_type' in packaging.
v41.2.0
-------
+
+
+Changes
+^^^^^^^
* #479: Remove some usage of the deprecated ``imp`` module.
+
+Misc
+^^^^
* #1565: Changed html_sidebars from string to list of string as per
https://www.sphinx-doc.org/en/master/changes.html#id58
@@ -454,6 +986,10 @@ v41.2.0
v41.1.0
-------
+
+
+Misc
+^^^^
* #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.
@@ -461,12 +997,19 @@ v41.1.0
* #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.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1776: Use license classifiers rather than the license field.
v41.0.1
-------
+
+
+Changes
+^^^^^^^
* #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
@@ -475,22 +1018,37 @@ v41.0.1
v41.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #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
-------
+
+
+Changes
+^^^^^^^
* #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.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1705: Removed some placeholder documentation sections referring to deprecated features.
v40.8.0
-------
+
+
+Changes
+^^^^^^^
* #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``.
@@ -499,25 +1057,44 @@ v40.8.0
v40.7.3
-------
+
+
+Changes
+^^^^^^^
* #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
-------
+
+
+Changes
+^^^^^^^
* #1666: Restore port in URL handling in package_index.
v40.7.1
-------
+
+
+Changes
+^^^^^^^
* #1660: On Python 2, when reading config files, downcast options from text to bytes to satisfy distutils expectations.
v40.7.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #1551: File inputs for the ``license`` field in ``setup.cfg`` files now explicitly raise an error.
+
+Changes
+^^^^^^^
* #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).
@@ -527,30 +1104,51 @@ v40.7.0
v40.6.3
-------
+
+
+Changes
+^^^^^^^
* #1594: PEP 517 backend no longer declares setuptools as a dependency as it can be assumed.
v40.6.2
-------
+
+
+Changes
+^^^^^^^
* #1592: Fix invalid dependency on external six module (instead of vendored version).
v40.6.1
-------
+
+
+Changes
+^^^^^^^
* #1590: Fixed regression where packages without ``author`` or ``author_email`` fields generated malformed package metadata.
v40.6.0
-------
+
+
+Deprecations
+^^^^^^^^^^^^
* #1541: Officially deprecated the ``requires`` parameter in ``setup()``.
+
+Changes
+^^^^^^^
* #1519: In ``pkg_resources.normalize_path``, additional path normalization is now performed to ensure path values to a directory is always the same, preventing false positives when checking scripts have a consistent prefix to set up on Windows.
* #1545: Changed the warning class of all deprecation warnings; deprecation warning classes are no longer derived from ``DeprecationWarning`` and are thus visible by default.
* #1554: ``build_meta.build_sdist`` now includes ``setup.py`` in source distributions by default.
* #1576: Started monkey-patching ``get_metadata_version`` and ``read_pkg_file`` onto ``distutils.DistributionMetadata`` to retain the correct version on the ``PKG-INFO`` file in the (deprecated) ``upload`` command.
-* #1533: Restricted the ``recursive-include setuptools/_vendor`` to contain only .py and .txt files.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1395: Changed Pyrex references to Cython in the documentation.
* #1456: Documented that the ``rpmbuild`` packages is required for the ``bdist_rpm`` command.
* #1537: Documented how to use ``setup.cfg`` for ``src/ layouts``
@@ -559,69 +1157,115 @@ v40.6.0
* #1553: Updated installation instructions to point to ``pip install`` instead of ``ez_setup.py``.
* #1560: Updated ``setuptools`` distribution documentation to remove some outdated information.
* #1564: Documented ``setup.cfg`` minimum version for version and project_urls.
+
+Misc
+^^^^
+* #1533: Restricted the ``recursive-include setuptools/_vendor`` to contain only .py and .txt files.
* #1572: Added the ``concurrent.futures`` backport ``futures`` to the Python 2.7 test suite requirements.
v40.5.0
-------
+
+
+Changes
+^^^^^^^
* #1335: In ``pkg_resources.normalize_path``, fix issue on Cygwin when cwd contains symlinks.
* #1502: Deprecated support for downloads from Subversion in package_index/easy_install.
* #1517: Dropped use of six.u in favor of ``u""`` literals.
* #1520: Added support for ``data_files`` in ``setup.cfg``.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1525: Fixed rendering of the deprecation warning in easy_install doc.
v40.4.3
-------
+
+
+Changes
+^^^^^^^
* #1480: Bump vendored pyparsing in pkg_resources to 2.2.1.
v40.4.2
-------
+
+
+Misc
+^^^^
* #1497: Updated gitignore in repo.
v40.4.1
-------
+
+
+Changes
+^^^^^^^
* #1480: Bump vendored pyparsing to 2.2.1.
v40.4.0
-------
+
+
+Changes
+^^^^^^^
* #1481: Join the sdist ``--dist-dir`` and the ``build_meta`` sdist directory argument to point to the same target (meaning the build frontend no longer needs to clean manually the dist dir to avoid multiple sdist presence, and setuptools no longer needs to handle conflicts between the two).
v40.3.0
-------
+
+
+Changes
+^^^^^^^
* #1402: Fixed a bug with namespace packages under Python 3.6 when one package in
current directory hides another which is installed.
* #1427: Set timestamp of ``.egg-info`` directory whenever ``egg_info`` command is run.
* #1474: ``build_meta.get_requires_for_build_sdist`` now does not include the ``wheel`` package anymore.
* #1486: Suppress warnings in pkg_resources.handle_ns.
+
+Misc
+^^^^
* #1479: Remove internal use of six.binary_type.
v40.2.0
-------
+
+
+Changes
+^^^^^^^
* #1466: Fix handling of Unicode arguments in PEP 517 backend
v40.1.1
--------
+
+
+Changes
+^^^^^^^
* #1465: Fix regression with ``egg_info`` command when tagging is used.
v40.1.0
-------
+
+
+Changes
+^^^^^^^
* #1410: Deprecated ``upload`` and ``register`` commands.
* #1312: Introduced find_namespace_packages() to find PEP 420 namespace packages.
* #1420: Added find_namespace: directive to config parser.
@@ -631,24 +1275,44 @@ v40.1.0
* #1388: Fixed "Microsoft Visual C++ Build Tools" link in exception when Visual C++ not found.
* #1389: Added support for scripts which have unicode content.
* #1416: Moved several Python version checks over to using ``six.PY2`` and ``six.PY3``.
+
+Misc
+^^^^
* #1441: Removed spurious executable permissions from files that don't need them.
v40.0.0
-------
+
+
+Breaking Changes
+^^^^^^^^^^^^^^^^
* #1342: Drop support for Python 3.3.
+
+Changes
+^^^^^^^
* #1366: In package_index, fixed handling of encoded entities in URLs.
* #1383: In pkg_resources VendorImporter, avoid removing packages imported from the root.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1379: Minor doc fixes after actually using the new release process.
* #1385: Removed section on non-package data files.
* #1403: Fix developer's guide.
+
+Misc
+^^^^
* #1404: Fix PEP 518 configuration: set build requirements in ``pyproject.toml`` to ``["wheel"]``.
v39.2.0
-------
+
+
+Changes
+^^^^^^^
* #1359: Support using "file:" to load a PEP 440-compliant package version from
a text file.
* #1360: Fixed issue with a mismatch between the name of the package and the
@@ -657,11 +1321,17 @@ v39.2.0
includes the attributes in the ``_provider`` instance variable.
* #1365: Take the package_dir option into account when loading the version from
a module attribute.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
* #1353: Added coverage badge to README.
* #1356: Made small fixes to the developer guide documentation.
* #1357: Fixed warnings in documentation builds and started enforcing that the
docs build without warnings in tox.
* #1376: Updated release process docs.
+
+Misc
+^^^^
* #1343: The ``setuptools`` specific ``long_description_content_type``,
``project_urls`` and ``provides_extras`` fields are now set consistently
after any ``distutils`` ``setup_keywords`` calls, allowing them to override
diff --git a/LICENSE b/LICENSE
index 6e0693b4..353924be 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,19 +1,19 @@
-Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com>
+Copyright Jason R. Coombs
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
index 128ae280..3e8f09de 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,10 +1,12 @@
-recursive-include setuptools *.py *.exe *.xml
+recursive-include setuptools *.py *.exe *.xml *.tmpl
recursive-include tests *.py
recursive-include setuptools/tests *.html
-recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
+recursive-include docs *.py *.txt *.rst *.conf *.css *.css_t Makefile indexsidebar.html
recursive-include setuptools/_vendor *.py *.txt
recursive-include pkg_resources *.py *.txt
recursive-include pkg_resources/tests/data *
+recursive-include tools *
+recursive-include changelog.d *
include *.py
include *.rst
include MANIFEST.in
@@ -13,4 +15,3 @@ 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 824a033f..9bd03cf9 100644
--- a/README.rst
+++ b/README.rst
@@ -6,14 +6,13 @@
.. _PyPI link: https://pypi.org/project/setuptools
-.. image:: https://dev.azure.com/jaraco/setuptools/_apis/build/status/pypa.setuptools?branchName=master
- :target: https://dev.azure.com/jaraco/setuptools/_build/latest?definitionId=1&branchName=master
+.. image:: https://github.com/pypa/setuptools/workflows/tests/badge.svg
+ :target: https://github.com/pypa/setuptools/actions?query=workflow%3A%22tests%22
+ :alt: tests
-.. 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%20CI&logo=appveyor&logoColor=white
- :target: https://ci.appveyor.com/project/pypa/setuptools/branch/master
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+ :target: https://github.com/psf/black
+ :alt: Code style: Black
.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg
:target: https://setuptools.readthedocs.io
@@ -35,9 +34,13 @@ 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.
+
+Code of Conduct
+===============
+
+Everyone interacting in the setuptools project's codebases, issue trackers,
+chat rooms, and mailing lists is expected to follow the
+`PSF Code of Conduct <https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
For Enterprise
@@ -49,9 +52,10 @@ Setuptools and the maintainers of thousands of other packages are working with T
`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
-`PSF Code of Conduct <https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
+Security Contact
+================
+
+To report a security vulnerability, please use the
+`Tidelift security contact <https://tidelift.com/security>`_.
+Tidelift will coordinate the fix and disclosure.
diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py
index c31edfed..47ce2494 100644
--- a/_distutils_hack/__init__.py
+++ b/_distutils_hack/__init__.py
@@ -8,6 +8,11 @@ import warnings
is_pypy = '__pypy__' in sys.builtin_module_names
+warnings.filterwarnings('ignore',
+ '.+ distutils .+ deprecated',
+ DeprecationWarning)
+
+
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 4d1ae55f..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-clone_depth: 50
-
-environment:
-
- APPVEYOR: True
- NETWORK_REQUIRED: True
- CODECOV_ENV: APPVEYOR_JOB_NAME
-
- matrix:
- - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
- APPVEYOR_JOB_NAME: "Python38-x64-vs2015"
- PYTHON: "C:\\Python38-x64"
- - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- APPVEYOR_JOB_NAME: "Python38-x64-vs2017"
- PYTHON: "C:\\Python38-x64"
- - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- APPVEYOR_JOB_NAME: "Python38-x64-vs2019"
- PYTHON: "C:\\Python38-x64"
- - APPVEYOR_JOB_NAME: "python37-x64"
- PYTHON: "C:\\Python37-x64"
- - APPVEYOR_JOB_NAME: "python36-x64"
- PYTHON: "C:\\Python36-x64"
- - APPVEYOR_JOB_NAME: "python35-x64"
- PYTHON: "C:\\Python35-x64"
- PYTEST_ADDOPTS: "--cov"
- TOX_TESTENV_PASSENV: "PYTEST_ADDOPTS"
-
-install:
- # symlink python from a directory with a space
- - "mklink /d \"C:\\Program Files\\Python\" %PYTHON%"
- - "SET PYTHON=\"C:\\Program Files\\Python\""
- - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
-
-build: off
-
-cache:
- - '%LOCALAPPDATA%\pip\Cache'
-
-test_script:
- - python --version
- - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
- - pip install --upgrade tox tox-venv virtualenv
- - pip freeze --all
- - tox -- --junit-xml=test-results.xml
-
-after_test:
- - tox -e coverage,codecov
-
-on_finish:
- - ps: |
- $wc = New-Object 'System.Net.WebClient'
- $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test-results.xml))
-
-version: '{build}'
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 4567b9b0..00000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-# Create the project in Azure with:
-# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public
-# then configure the pipelines (through web UI)
-
-trigger:
- branches:
- include:
- - '*'
- tags:
- include:
- - '*'
-
-pool:
- vmImage: $(pool_vm_image)
-
-variables:
-- group: Azure secrets
-- name: pool_vm_image
- value: Ubuntu-18.04
-
-stages:
-- stage: Test
- jobs:
-
- - job: 'Test'
- strategy:
- matrix:
- Bionic Python 3.6:
- python.version: '3.6'
- Bionic Python 3.8:
- python.version: '3.8'
- Windows:
- python.version: '3.8'
- pool_vm_image: vs2017-win2016
- MacOS:
- python.version: '3.8'
- pool_vm_image: macos-10.15
-
- maxParallel: 4
-
- steps:
- - task: UsePythonVersion@0
- inputs:
- versionSpec: '$(python.version)'
- architecture: 'x64'
-
- - script: python -m pip install tox
- displayName: 'Install tox'
-
- - script: |
- tox -- --junit-xml=test-results.xml
- displayName: 'run tests'
-
- - task: PublishTestResults@2
- inputs:
- testResultsFiles: '**/test-results.xml'
- testRunTitle: 'Python $(python.version)'
- condition: succeededOrFailed()
-
-- stage: Publish
- dependsOn: Test
- jobs:
- - job: 'Publish'
-
- steps:
- - task: UsePythonVersion@0
- inputs:
- versionSpec: '3.8'
- architecture: 'x64'
-
- - script: python -m pip install tox
- displayName: 'Install tox'
-
- - script: |
- tox -e release
- env:
- TWINE_PASSWORD: $(PyPI-token)
- TIDELIFT_TOKEN: $(Tidelift-token)
- GITHUB_TOKEN: $(Github-token)
- displayName: 'publish to PyPI'
-
- condition: contains(variables['Build.SourceBranch'], 'tags')
diff --git a/bootstrap.egg-info/PKG-INFO b/bootstrap.egg-info/PKG-INFO
new file mode 100644
index 00000000..6e11ceeb
--- /dev/null
+++ b/bootstrap.egg-info/PKG-INFO
@@ -0,0 +1,2 @@
+Name: setuptools-bootstrap
+Version: 1.0
diff --git a/bootstrap.egg-info/entry_points.txt b/bootstrap.egg-info/entry_points.txt
new file mode 100644
index 00000000..834d674e
--- /dev/null
+++ b/bootstrap.egg-info/entry_points.txt
@@ -0,0 +1,14 @@
+[distutils.commands]
+egg_info = setuptools.command.egg_info:egg_info
+
+[distutils.setup_keywords]
+include_package_data = setuptools.dist:assert_bool
+install_requires = setuptools.dist:check_requirements
+extras_require = setuptools.dist:check_extras
+entry_points = setuptools.dist:check_entry_points
+
+[egg_info.writers]
+PKG-INFO = setuptools.command.egg_info:write_pkg_info
+dependency_links.txt = setuptools.command.egg_info:overwrite_arg
+entry_points.txt = setuptools.command.egg_info:write_entries
+requires.txt = setuptools.command.egg_info:write_requirements
diff --git a/bootstrap.py b/bootstrap.py
index 118671f6..229b9965 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -1,57 +1,7 @@
-"""
-If setuptools is not already installed in the environment, it's not possible
-to invoke setuptools' own commands. This routine will bootstrap this local
-environment by creating a minimal egg-info directory and then invoking the
-egg-info command to flesh out the egg-info directory.
-"""
+import warnings
-import os
-import sys
-import textwrap
-import subprocess
-import io
+msg = "bootstrap.py is no longer needed. Use a PEP-517-compatible builder instead."
-minimal_egg_info = textwrap.dedent("""
- [distutils.commands]
- egg_info = setuptools.command.egg_info:egg_info
- [distutils.setup_keywords]
- include_package_data = setuptools.dist:assert_bool
- install_requires = setuptools.dist:check_requirements
- extras_require = setuptools.dist:check_extras
- entry_points = setuptools.dist:check_entry_points
-
- [egg_info.writers]
- PKG-INFO = setuptools.command.egg_info:write_pkg_info
- dependency_links.txt = setuptools.command.egg_info:overwrite_arg
- entry_points.txt = setuptools.command.egg_info:write_entries
- requires.txt = setuptools.command.egg_info:write_requirements
- """)
-
-
-def ensure_egg_info():
- if os.path.exists('setuptools.egg-info'):
- return
- print("adding minimal entry_points")
- add_minimal_info()
- run_egg_info()
-
-
-def add_minimal_info():
- """
- Build a minimal egg-info, enough to invoke egg_info
- """
-
- os.mkdir('setuptools.egg-info')
- with io.open('setuptools.egg-info/entry_points.txt', 'w') as ep:
- ep.write(minimal_egg_info)
-
-
-def run_egg_info():
- cmd = [sys.executable, 'setup.py', 'egg_info']
- print("Regenerating egg_info")
- subprocess.check_call(cmd)
-
-
-__name__ == '__main__' and ensure_egg_info()
+__name__ == '__main__' and warnings.warn(msg)
diff --git a/changelog.d/2430.doc.1.rst b/changelog.d/2430.doc.1.rst
deleted file mode 100644
index d09e0b9e..00000000
--- a/changelog.d/2430.doc.1.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed a typo in Sphinx docs that made docs dev section disappear
-as a result of PR #2426 -- by :user:`webknjaz`
diff --git a/changelog.d/2430.doc.2.rst b/changelog.d/2430.doc.2.rst
deleted file mode 100644
index 0ac87823..00000000
--- a/changelog.d/2430.doc.2.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed inconsistent RST title nesting levels caused by #2399
--- by :user:`webknjaz`
diff --git a/changelog.d/2644.misc.rst b/changelog.d/2644.misc.rst
new file mode 100644
index 00000000..1aa9fbbe
--- /dev/null
+++ b/changelog.d/2644.misc.rst
@@ -0,0 +1 @@
+Fixed ``DeprecationWarning`` due to ``threading.Thread.setDaemon`` in tests -- by :user:`tirkarthi`
diff --git a/changelog.d/2654.misc.rst b/changelog.d/2654.misc.rst
new file mode 100644
index 00000000..268c4a2e
--- /dev/null
+++ b/changelog.d/2654.misc.rst
@@ -0,0 +1,2 @@
+Made the changelog generator compatible
+with Towncrier >= 19.9 -- :user:`webknjaz`
diff --git a/docs/_templates/tidelift-sidebar.html b/docs/_templates/tidelift-sidebar.html
new file mode 100644
index 00000000..ce48f46b
--- /dev/null
+++ b/docs/_templates/tidelift-sidebar.html
@@ -0,0 +1,6 @@
+<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>
diff --git a/docs/build_meta.rst b/docs/build_meta.rst
index c36e2bab..2ad5ae26 100644
--- a/docs/build_meta.rst
+++ b/docs/build_meta.rst
@@ -7,7 +7,7 @@ What is it?
Python packaging has come `a long way <https://www.bernat.tech/pep-517-518/>`_.
-The traditional ``setuptools`` way of packgaging Python modules
+The traditional ``setuptools`` way of packaging Python modules
uses a ``setup()`` function within the ``setup.py`` script. Commands such as
``python setup.py bdist`` or ``python setup.py bdist_wheel`` generate a
distribution bundle and ``python setup.py install`` installs the distribution.
@@ -67,14 +67,11 @@ specify the package information::
[options]
packages = find:
-Now generate the distribution. Although the PyPA is still working to
-`provide a recommended tool <https://github.com/pypa/packaging-problems/issues/219>`_
-to build packages, the `pep517 package <https://pypi.org/project/pep517>`_
-provides this functionality. To build the package::
+Now generate the distribution. To build the package, use
+`PyPA build <https://pypa-build.readthedocs.io/en/latest/>`_::
- $ pip install -q pep517
- $ mkdir dist
- $ python -m pep517.build .
+ $ pip install -q build
+ $ python -m build
And now it's done! The ``.whl`` file and ``.tar.gz`` can then be distributed
and installed::
diff --git a/docs/conf.py b/docs/conf.py
index de9fe3f8..65078c96 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,81 +1,6 @@
-import subprocess
-import sys
-import os
+extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker']
-
-# hack to run the bootstrap script so that jaraco.packaging.sphinx
-# can invoke setup.py
-'READTHEDOCS' in os.environ and subprocess.check_call(
- [sys.executable, '-m', 'bootstrap'],
- cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
-)
-
-# -- Project information -----------------------------------------------------
-
-github_url = 'https://github.com'
-github_sponsors_url = f'{github_url}/sponsors'
-
-# -- General configuration --
-
-extensions = [
- 'sphinx.ext.extlinks', # allows to create custom roles easily
- 'sphinx.ext.intersphinx', # allows interlinking external docs sites
- 'jaraco.packaging.sphinx',
- 'rst.linker',
-]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The master toctree document.
-master_doc = 'index'
-
-# List of directories, relative to source directory, that shouldn't be searched
-# for source files.
-exclude_trees = []
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# -- Options for extlinks extension ---------------------------------------
-extlinks = {
- 'user': (f'{github_sponsors_url}/%s', '@'), # noqa: WPS323
-}
-
-# -- Options for HTML output --
-
-# The theme to use for HTML and HTML Help pages. Major themes that come with
-# Sphinx are currently 'default' and 'sphinxdoc'.
-html_theme = 'nature'
-
-# Add any paths that contain custom themes here, relative to this directory.
-html_theme_path = ['_theme']
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-html_sidebars = {
- 'index': [
- 'relations.html', 'sourcelink.html', 'indexsidebar.html',
- 'searchbox.html']}
-
-# If false, no module index is generated.
-html_use_modindex = False
-
-# If false, no index is generated.
-html_use_index = False
-
-# -- Options for LaTeX output --
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author,
-# documentclass [howto/manual]).
-latex_documents = [(
- 'index', 'Setuptools.tex', 'Setuptools Documentation',
- 'The fellowship of the packaging', 'manual',
-)]
+master_doc = "index"
link_files = {
'../CHANGES.rst': dict(
@@ -148,10 +73,38 @@ link_files = {
),
}
+intersphinx_mapping = {
+ 'pypa-build': ('https://pypa-build.readthedocs.io/en/latest/', None)
+}
+
+# Add support for linking usernames
+github_url = 'https://github.com'
+github_sponsors_url = f'{github_url}/sponsors'
+extlinks = {
+ 'user': (f'{github_sponsors_url}/%s', '@'), # noqa: WPS323
+}
+extensions += ['sphinx.ext.extlinks', 'sphinx.ext.intersphinx']
# Be strict about any broken references:
nitpicky = True
+# Ref: https://github.com/python-attrs/attrs/pull/571/files\
+# #diff-85987f48f1258d9ee486e3191495582dR82
+default_role = 'any'
+
+# Custom sidebar templates, maps document names to template names.
+html_theme = 'alabaster'
+templates_path = ['_templates']
+html_sidebars = {'index': ['tidelift-sidebar.html']}
+
+# Add support for inline tabs
+extensions += ['sphinx_inline_tabs']
+
+## Support for distutils
+
+# allow interlinking external docs sites
+extensions += ['sphinx.ext.intersphinx'],
+
# Ref: https://stackoverflow.com/a/30624034/595220
nitpick_ignore = [
('c:func', 'SHGetSpecialFolderPath'), # ref to MS docs
@@ -188,12 +141,6 @@ nitpick_ignore = [
('py:mod', 'docutils'), # there's no Sphinx site documenting this
]
-
-# Ref: https://github.com/python-attrs/attrs/pull/571/files\
-# #diff-85987f48f1258d9ee486e3191495582dR82
-default_role = 'any'
-
-
# Allow linking objects on other Sphinx sites seamlessly:
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
diff --git a/docs/pkg_resources.rst b/docs/pkg_resources.rst
index 364e2183..994bea6f 100644
--- a/docs/pkg_resources.rst
+++ b/docs/pkg_resources.rst
@@ -10,6 +10,13 @@ eggs, support for merging packages that have separately-distributed modules or
subpackages, and APIs for managing Python's current "working set" of active
packages.
+Use of ``pkg_resources`` is discouraged in favor of
+`importlib.resources <https://docs.python.org/3/library/importlib.html#module-importlib.resources>`_,
+`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_,
+and their backports (`resources <https://pypi.org/project/importlib_resources>`_,
+`metadata <https://pypi.org/project/importlib_metadata>`_).
+Please consider using those libraries instead of pkg_resources.
+
.. contents:: **Table of Contents**
@@ -703,7 +710,7 @@ entry point group and look for entry points named "pre_process" and
To advertise an entry point, a project needs to use ``setuptools`` and provide
an ``entry_points`` argument to ``setup()`` in its setup script, so that the
entry points will be included in the distribution's metadata. For more
-details, see the [``setuptools`` documentation](https://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins).
+details, see :ref:`Advertising Behavior<dynamic discovery of services and plugins>`.
Each project distribution can advertise at most one entry point of a given
name within the same entry point group. For example, a distutils extension
@@ -1939,4 +1946,3 @@ History
0.3a1
* Initial release.
-
diff --git a/docs/references/keywords.rst b/docs/references/keywords.rst
index 03ce9fa2..619b2d14 100644
--- a/docs/references/keywords.rst
+++ b/docs/references/keywords.rst
@@ -76,6 +76,17 @@ Keywords
``license``
A string specifying the license of the package.
+``license_file``
+
+ .. warning::
+ ``license_file`` is deprecated. Use ``license_files`` instead.
+
+``license_files``
+
+ A list of glob patterns for license related files that should be included.
+ If neither ``license_file`` nor ``license_files`` is specified, this option
+ defaults to ``LICEN[CS]E*``, ``COPYING*``, ``NOTICE*``, and ``AUTHORS*``.
+
``keywords``
A list of strings or a comma-separated string providing descriptive
meta-data. See: `PEP 0314`_.
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 104d68fa..00000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-# keep these in sync with setup.cfg
-sphinx
-jaraco.packaging>=6.1
-rst.linker>=1.9
-pygments-github-lexers==0.0.5
-
-setuptools>=34
diff --git a/docs/setuptools.rst b/docs/setuptools.rst
index 1000a0ce..541bec51 100644
--- a/docs/setuptools.rst
+++ b/docs/setuptools.rst
@@ -54,7 +54,7 @@ Feature Highlights:
Developer's Guide
-----------------
-
+The developer's guide has been updated. See the :doc:`most recent version <userguide/index>`.
@@ -157,7 +157,7 @@ To use this feature:
]
build-backend = "setuptools.build_meta"
-* Use a :pep:`517` compatible build frontend, such as ``pip >= 19`` or ``pep517``.
+* Use a :pep:`517` compatible build frontend, such as ``pip >= 19`` or ``build``.
.. warning::
diff --git a/docs/userguide/declarative_config.rst b/docs/userguide/declarative_config.rst
index bc66869b..7c97ca1c 100644
--- a/docs/userguide/declarative_config.rst
+++ b/docs/userguide/declarative_config.rst
@@ -184,7 +184,7 @@ maintainer_email maintainer-email str
classifiers classifier file:, list-comma
license str
license_file str
-license_files list-comma
+license_files list-comma 42.0.0
description summary file:, str
long_description long-description file:, str
long_description_content_type str 38.6.0
@@ -243,6 +243,19 @@ 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``.
+
+2. In the ``extras_require`` section, values are parsed as ``list-semi``. This implies that in
+order to include markers, they **must** be *dangling*:
+
+.. code-block:: ini
+
+ [options.extras_require]
+ rest = docutils>=0.3; pack ==1.1, ==1.3
+ pdf =
+ ReportLab>=1.2
+ RXP
+ importlib-metadata; python_version < "3.8"
diff --git a/docs/userguide/dependency_management.rst b/docs/userguide/dependency_management.rst
index 354a9f8c..188083e0 100644
--- a/docs/userguide/dependency_management.rst
+++ b/docs/userguide/dependency_management.rst
@@ -3,7 +3,7 @@ Dependencies Management in Setuptools
=====================================
There are three types of dependency styles offered by setuptools:
-1) build system requirement, required dependency and 3) optional
+1) build system requirement, 2) required dependency and 3) optional
dependency.
.. Note::
@@ -19,8 +19,8 @@ Build system requirement
Package requirement
-------------------
After organizing all the scripts and files and getting ready for packaging,
-there needs to be a way to tell Python what programs it need to actually
-do the packgaging (in our case, ``setuptools`` of course). Usually,
+there needs to be a way to tell Python what programs it needs to actually
+do the packaging (in our case, ``setuptools`` of course). Usually,
you also need the ``wheel`` package as well since it is recommended that you
upload a ``.whl`` file to PyPI alongside your ``.tar.gz`` file. Unlike the
other two types of dependency keyword, this one is specified in your
@@ -47,32 +47,36 @@ Declaring required dependency
This is where a package declares its core dependencies, without which it won't
be able to run. ``setuptools`` support automatically download and install
these dependencies when the package is installed. Although there is more
-finess to it, let's start with a simple example.
+finesse to it, let's start with a simple example.
-.. code-block:: ini
+.. tab:: setup.cfg
- [options]
- #...
- install_requires =
- docutils
- BazSpam ==1.1
+ .. code-block:: ini
+
+ [options]
+ #...
+ install_requires =
+ docutils
+ BazSpam ==1.1
+
+.. tab:: setup.py
-.. code-block:: python
+ .. code-block:: python
- setup(
- #...,
- install_requires = [
- 'docutils',
- 'BazSpam ==1.1'
- ]
- )
+ setup(
+ #...,
+ install_requires = [
+ 'docutils',
+ 'BazSpam ==1.1'
+ ]
+ )
When your project is installed (e.g. using pip), all of the dependencies not
already installed will be located (via PyPI), downloaded, built (if necessary),
and installed and 2) Any scripts in your project will be installed with wrappers
that verify the availability of the specified dependencies at runtime.
-
+
Platform specific dependencies
------------------------------
@@ -82,41 +86,49 @@ specific dependencies. For example, the ``enum`` package was added in Python
3.4, therefore, package that depends on it can elect to install it only when
the Python version is older than 3.4. To accomplish this
-.. code-block:: ini
+.. tab:: setup.cfg
- [options]
- #...
- install_requires =
- enum34;python_version<'3.4'
+ .. code-block:: ini
-.. code-block:: python
-
- setup(
+ [options]
#...
- install_requires=[
- "enum34;python_version<'3.4'",]
- )
+ install_requires =
+ enum34;python_version<'3.4'
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ #...
+ install_requires=[
+ "enum34;python_version<'3.4'",]
+ )
Similarly, if you also wish to declare ``pywin32`` with a minimal version of 1.0
and only install it if the user is using a Windows operating system:
-.. code-block:: ini
-
- [options]
- #...
- install_requires =
- enum34;python_version<'3.4'
- pywin32 >= 1.0;platform_system=='Windows'
+.. tab:: setup.cfg
-.. code-block:: python
+ .. code-block:: ini
- setup(
+ [options]
#...
- install_requires=[
- "enum34;python_version<'3.4'",
- "pywin32 >= 1.0;platform_system=='Windows'"
- ]
- )
+ install_requires =
+ enum34;python_version<'3.4'
+ pywin32 >= 1.0;platform_system=='Windows'
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ #...
+ install_requires=[
+ "enum34;python_version<'3.4'",
+ "pywin32 >= 1.0;platform_system=='Windows'"
+ ]
+ )
The environmental markers that may be used for testing platform types are
detailed in `PEP 508 <https://www.python.org/dev/peps/pep-0508/>`_.
@@ -181,20 +193,24 @@ The ``dependency_links`` option takes the form of a list of URL strings. For
example, this will cause a search of the specified page for eggs or source
distributions, if the package's dependencies aren't already installed:
-.. code-block:: ini
-
- [options]
- #...
- dependency_links = http://peak.telecommunity.com/snapshots/
+.. tab:: setup.cfg
-.. code-block:: python
+ .. code-block:: ini
- setup(
+ [options]
#...
- dependency_links=[
- "http://peak.telecommunity.com/snapshots/"
- ],
- )
+ dependency_links = http://peak.telecommunity.com/snapshots/
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ #...
+ dependency_links=[
+ "http://peak.telecommunity.com/snapshots/"
+ ],
+ )
Optional dependencies
@@ -202,7 +218,7 @@ Optional dependencies
Setuptools allows you to declare dependencies that only get installed under
specific circumstances. These dependencies are specified with ``extras_require``
keyword and are only installed if another package depends on it (either
-directly or indirectly) This makes it convenient to declare dependencies for
+directly or indirectly) This makes it convenient to declare dependencies for
ancillary functions such as "tests" and "docs".
.. note::
@@ -211,24 +227,28 @@ ancillary functions such as "tests" and "docs".
For example, Package-A offers optional PDF support and requires two other
dependencies for it to work:
-.. code-block:: ini
+.. tab:: setup.cfg
- [metadata]
- name = Package-A
+ .. code-block:: ini
- [options.extras_require]
- PDF = ReportLab>=1.2; RXP
+ [metadata]
+ name = Package-A
+ [options.extras_require]
+ PDF = ReportLab>=1.2; RXP
-.. code-block:: python
- setup(
- name="Project-A",
- #...
- extras_require={
- "PDF": ["ReportLab>=1.2", "RXP"],
- }
- )
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ name="Project-A",
+ #...
+ extras_require={
+ "PDF": ["ReportLab>=1.2", "RXP"],
+ }
+ )
The name ``PDF`` is an arbitary identifier of such a list of dependencies, to
which other components can refer and have them installed. There are two common
@@ -236,57 +256,69 @@ use cases.
First is the console_scripts entry point:
-.. code-block:: ini
+.. tab:: setup.cfg
- [metadata]
- name = Project A
- #...
+ .. code-block:: ini
- [options]
- #...
- entry_points=
- [console_scripts]
- rst2pdf = project_a.tools.pdfgen [PDF]
- rst2html = project_a.tools.htmlgen
-
-.. code-block:: python
-
- setup(
- name = "Project-A"
- #...,
- entry_points={
- "console_scripts": [
- "rst2pdf = project_a.tools.pdfgen [PDF]",
- "rst2html = project_a.tools.htmlgen",
- ],
- }
- )
+ [metadata]
+ name = Project A
+ #...
-When the script ``rst2pdf`` is run, it will trigger the installation of
-the two dependencies ``PDF`` maps to.
+ [options]
+ #...
+ entry_points=
+ [console_scripts]
+ rst2pdf = project_a.tools.pdfgen [PDF]
+ rst2html = project_a.tools.htmlgen
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ name = "Project-A"
+ #...,
+ entry_points={
+ "console_scripts": [
+ "rst2pdf = project_a.tools.pdfgen [PDF]",
+ "rst2html = project_a.tools.htmlgen",
+ ],
+ }
+ )
+
+This syntax indicates that the entry point (in this case a console script)
+is only valid when the PDF extra is installed. It is up to the installer
+to determine how to handle the situation where PDF was not indicated
+(e.g. omit the console script, provide a warning when attempting to load
+the entry point, assume the extras are present and let the implementation
+fail later).
The second use case is that other package can use this "extra" for their
own dependencies. For example, if "Project-B" needs "project A" with PDF support
installed, it might declare the dependency like this:
-.. code-block:: ini
+.. tab:: setup.cfg
- [metadata]
- name = Project-B
- #...
+ .. code-block:: ini
- [options]
- #...
- install_requires =
- Project-A[PDF]
+ [metadata]
+ name = Project-B
+ #...
+
+ [options]
+ #...
+ install_requires =
+ Project-A[PDF]
+
+.. tab:: setup.py
-.. code-block:: python
+ .. code-block:: python
- setup(
- name="Project-B",
- install_requires=["Project-A[PDF]"],
- ...
- )
+ setup(
+ name="Project-B",
+ install_requires=["Project-A[PDF]"],
+ ...
+ )
This will cause ReportLab to be installed along with project A, if project B is
installed -- even if project A was already installed. In this way, a project
diff --git a/docs/userguide/development_mode.rst b/docs/userguide/development_mode.rst
index bce724a7..3c477ec1 100644
--- a/docs/userguide/development_mode.rst
+++ b/docs/userguide/development_mode.rst
@@ -3,9 +3,9 @@
Under normal circumstances, the ``distutils`` assume that you are going to
build a distribution of your project, not use it in its "raw" or "unbuilt"
-form. If you were to use the ``distutils`` that way, you would have to rebuild
-and reinstall your project every time you made a change to it during
-development.
+form. However, if you were to use the ``distutils`` to build a distribution,
+you would have to rebuild and reinstall your project every time you made a
+change to it during development.
Another problem that sometimes comes up with the ``distutils`` is that you may
need to do development on two related projects at the same time. You may need
diff --git a/docs/userguide/entry_point.rst b/docs/userguide/entry_point.rst
index edab4465..63d30a48 100644
--- a/docs/userguide/entry_point.rst
+++ b/docs/userguide/entry_point.rst
@@ -28,7 +28,7 @@ with ``__init__.py`` as:
.. code-block:: python
- def helloworld():
+ def hello_world():
print("Hello world")
and ``__main__.py`` providing a hook:
@@ -64,7 +64,7 @@ After installing the package, a user may invoke that function by simply calling
The syntax for entry points is specified as follows:
-.. code-block::
+.. code-block:: ini
<name> = [<package>.[<subpackage>.]]<module>[:<object>.<object>]
diff --git a/docs/userguide/package_discovery.rst b/docs/userguide/package_discovery.rst
index de4ef668..0a8070ae 100644
--- a/docs/userguide/package_discovery.rst
+++ b/docs/userguide/package_discovery.rst
@@ -19,36 +19,44 @@ Package Discovery and Namespace Package
support for namespace package. Normally, you would specify the package to be
included manually in the following manner:
-.. code-block:: ini
-
- [options]
- #...
- packages =
- mypkg1
- mypkg2
+.. tab:: setup.cfg
-.. code-block:: python
+ .. code-block:: ini
- setup(
+ [options]
#...
- packages = ['mypkg1', 'mypkg2']
- )
+ packages =
+ mypkg1
+ mypkg2
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ #...
+ packages = ['mypkg1', 'mypkg2']
+ )
This can get tiresome reallly quickly. To speed things up, we introduce two
functions provided by setuptools:
-.. code-block:: ini
+.. tab:: setup.cfg
- [options]
- packages = find:
- #or
- packages = find_namespace:
+ .. code-block:: ini
-.. code-block:: python
+ [options]
+ packages = find:
+ #or
+ packages = find_namespace:
+
+.. tab:: setup.py
- from setuptools import find_packages
- #or
- from setuptools import find_namespace_packages
+ .. code-block:: python
+
+ from setuptools import find_packages
+ #or
+ from setuptools import find_namespace_packages
Using ``find:`` or ``find_packages``
@@ -71,30 +79,34 @@ it, consider the following directory
To have your setup.cfg or setup.py to automatically include packages found
in ``src`` that starts with the name ``pkg`` and not ``additional``:
-.. code-block:: ini
+.. tab:: setup.cfg
- [options]
- packages = find:
- package_dir =
- =src
+ .. code-block:: ini
- [options.packages.find]
- where = src
- include = pkg*
- exclude = additional
+ [options]
+ packages = find:
+ package_dir =
+ =src
-.. code-block:: python
+ [options.packages.find]
+ where = src
+ include = pkg*
+ exclude = additional
- setup(
- #...
- packages = find_packages(
- where = 'src',
- include = ['pkg*',],
- exclude = ['additional',]
- ),
- package_dir = {"":"src"}
- #...
- )
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ setup(
+ #...
+ packages = find_packages(
+ where = 'src',
+ include = ['pkg*',],
+ exclude = ['additional',]
+ ),
+ package_dir = {"":"src"}
+ #...
+ )
.. _Namespace Packages:
@@ -144,7 +156,7 @@ to use ``find_namespace:``:
=src
packages = find_namespace:
- [options.packages.find_namespace]
+ [options.packages.find]
where = src
When you install the zipped distribution, ``timmins.foo`` would become
@@ -195,17 +207,21 @@ following:
And the ``namespace_packages`` keyword in your ``setup.cfg`` or ``setup.py``:
-.. code-block:: ini
+.. tab:: setup.cfg
- [options]
- namespace_packages = timmins
+ .. code-block:: ini
-.. code-block:: python
+ [options]
+ namespace_packages = timmins
+
+.. tab:: setup.py
+
+ .. code-block:: python
- setup(
- # ...
- namespace_packages = ['timmins']
- )
+ setup(
+ # ...
+ namespace_packages = ['timmins']
+ )
And your directory should look like this
diff --git a/docs/userguide/quickstart.rst b/docs/userguide/quickstart.rst
index 697087ed..2807f59b 100644
--- a/docs/userguide/quickstart.rst
+++ b/docs/userguide/quickstart.rst
@@ -21,7 +21,7 @@ the backend (build system) it wants to use. The distribution can then
be generated with whatever tools that provides a ``build sdist``-alike
functionality. While this may appear cumbersome, given the added pieces,
it in fact tremendously enhances the portability of your package. The
-change is driven under :pep:`517 <517#build-requirements>`. To learn more about Python packaging in general,
+change is driven under :pep:`PEP 517 <517#build-requirements>`. To learn more about Python packaging in general,
navigate to the `bottom <Resources on python packaging>`_ of this page.
@@ -37,33 +37,52 @@ package your project:
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
-Then, you will need a ``setup.cfg`` to specify your package information,
-such as metadata, contents, dependencies, etc. Here we demonstrate the minimum
+Then, you will need a ``setup.cfg`` or ``setup.py`` to specify your package
+information, such as metadata, contents, dependencies, etc. Here we demonstrate
+the minimum
-.. code-block:: ini
+.. tab:: setup.cfg
- [metadata]
- name = "mypackage"
- version = 0.0.1
+ .. code-block:: ini
- [options]
- packages = "mypackage"
- install_requires =
- requests
- importlib; python_version == "2.6"
+ [metadata]
+ name = mypackage
+ version = 0.0.1
+
+ [options]
+ packages = mypackage
+ install_requires =
+ requests
+ importlib; python_version == "2.6"
+
+.. tab:: setup.py
+
+ .. code-block:: python
+
+ from setuptools import setup
+
+ setup(
+ name='mypackage',
+ version='0.0.1',
+ packages=['mypackage'],
+ install_requires=[
+ 'requests',
+ 'importlib; python_version == "2.6"',
+ ],
+ )
This is what your project would look like::
~/mypackage/
pyproject.toml
- setup.cfg
+ setup.cfg # or setup.py
mypackage/__init__.py
-Then, you need an installer, such as `pep517 <https://pypi.org/project/pep517/>`_
-which you can obtain via ``pip install pep517``. After downloading it, invoke
-the installer::
+Then, you need an builder, such as :std:doc:`PyPA build <pypa-build:index>`
+which you can obtain via ``pip install build``. After downloading it, invoke
+the builder::
- python -m pep517.build
+ python -m build
You now have your distribution ready (e.g. a ``tar.gz`` file and a ``.whl``
file in the ``dist`` directory), which you can upload to PyPI!
@@ -71,7 +90,7 @@ file in the ``dist`` directory), which you can upload to PyPI!
Of course, before you release your project to PyPI, you'll want to add a bit
more information to your setup script to help people find or learn about your
project. And maybe your project will have grown by then to include a few
-dependencies, and perhaps some data files and scripts. In the next few section,
+dependencies, and perhaps some data files and scripts. In the next few sections,
we will walk through those additional but essential information you need
to specify to properly package your project.
diff --git a/easy_install.py b/easy_install.py
deleted file mode 100644
index d87e9840..00000000
--- a/easy_install.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""Run the EasyInstall command"""
-
-if __name__ == '__main__':
- from setuptools.command.easy_install import main
- main()
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..976ba029
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,2 @@
+[mypy]
+ignore_missing_imports = True
diff --git a/netlify.toml b/netlify.toml
deleted file mode 100644
index 5828132e..00000000
--- a/netlify.toml
+++ /dev/null
@@ -1,7 +0,0 @@
-# Configuration for pull request documentation previews via Netlify
-
-# Netlify relies on there being a ./runtime.txt to indicate Python 3.
-
-[build]
- publish = "build/html"
- command = "pip install tox && tox -e docs"
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 737f4d5f..c84f1dd9 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -38,6 +38,7 @@ import itertools
import inspect
import ntpath
import posixpath
+import importlib
from pkgutil import get_importer
try:
@@ -696,7 +697,8 @@ class WorkingSet:
keys2.append(dist.key)
self._added_new(dist)
- def resolve(self, requirements, env=None, installer=None,
+ # FIXME: 'WorkingSet.resolve' is too complex (11)
+ def resolve(self, requirements, env=None, installer=None, # noqa: C901
replace_conflicting=False, extras=None):
"""List all distributions needed to (recursively) meet `requirements`
@@ -1745,7 +1747,8 @@ class ZipProvider(EggProvider):
timestamp = time.mktime(date_time)
return timestamp, size
- def _extract_resource(self, manager, zip_path):
+ # FIXME: 'ZipProvider._extract_resource' is too complex (12)
+ def _extract_resource(self, manager, zip_path): # noqa: C901
if zip_path in self._index():
for name in self._index()[zip_path]:
@@ -1983,7 +1986,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
for dist in dists:
yield dist
- elif subitem.lower().endswith('.dist-info'):
+ elif subitem.lower().endswith(('.dist-info', '.egg-info')):
subpath = os.path.join(path_item, subitem)
submeta = EggMetadata(zipimport.zipimporter(subpath))
submeta.egg_info = subpath
@@ -2209,7 +2212,7 @@ def _handle_ns(packageName, path_item):
if subpath is not None:
path = module.__path__
path.append(subpath)
- loader.load_module(packageName)
+ importlib.import_module(packageName)
_rebuild_mod_path(path, packageName, module)
return subpath
@@ -2858,7 +2861,8 @@ class Distribution:
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return self.get_entry_map(group).get(name)
- def insert_on(self, path, loc=None, replace=False):
+ # FIXME: 'Distribution.insert_on' is too complex (13)
+ def insert_on(self, path, loc=None, replace=False): # noqa: C901
"""Ensure self.location is on path
If replace=False (default):
diff --git a/pkg_resources/extern/__init__.py b/pkg_resources/extern/__init__.py
index 4dc3beb2..fed59295 100644
--- a/pkg_resources/extern/__init__.py
+++ b/pkg_resources/extern/__init__.py
@@ -1,3 +1,4 @@
+import importlib.util
import sys
@@ -20,17 +21,10 @@ class VendorImporter:
yield self.vendor_pkg + '.'
yield ''
- def find_module(self, fullname, path=None):
- """
- Return self when fullname starts with root_name and the
- target module is one vendored through this importer.
- """
+ def _module_matches_namespace(self, fullname):
+ """Figure out if the target module is vendored."""
root, base, target = fullname.partition(self.root_name + '.')
- if root:
- return
- if not any(map(target.startswith, self.vendored_names)):
- return
- return self
+ return not root and any(map(target.startswith, self.vendored_names))
def load_module(self, fullname):
"""
@@ -54,6 +48,19 @@ class VendorImporter:
"distribution.".format(**locals())
)
+ def create_module(self, spec):
+ return self.load_module(spec.name)
+
+ def exec_module(self, module):
+ pass
+
+ def find_spec(self, fullname, path=None, target=None):
+ """Return a module spec for vendored names."""
+ return (
+ importlib.util.spec_from_loader(fullname, self)
+ if self._module_matches_namespace(fullname) else None
+ )
+
def install(self):
"""
Install this importer into sys.meta_path if not already present.
diff --git a/pkg_resources/tests/data/my-test-package-zip/my-test-package.zip b/pkg_resources/tests/data/my-test-package-zip/my-test-package.zip
new file mode 100644
index 00000000..81f9a017
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package-zip/my-test-package.zip
Binary files differ
diff --git a/pkg_resources/tests/test_find_distributions.py b/pkg_resources/tests/test_find_distributions.py
index f9594422..b01b4827 100644
--- a/pkg_resources/tests/test_find_distributions.py
+++ b/pkg_resources/tests/test_find_distributions.py
@@ -32,3 +32,12 @@ class TestFindDistributions:
assert [dist.project_name for dist in dists] == ['my-test-package']
dists = pkg_resources.find_distributions(str(target_dir), only=True)
assert not list(dists)
+
+ def test_zipped_sdist_one_level_removed(self, target_dir):
+ (TESTS_DATA_DIR / 'my-test-package-zip').copy(target_dir)
+ dists = pkg_resources.find_distributions(
+ str(target_dir / "my-test-package.zip"))
+ assert [dist.project_name for dist in dists] == ['my-test-package']
+ dists = pkg_resources.find_distributions(
+ str(target_dir / "my-test-package.zip"), only=True)
+ assert not list(dists)
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index b08bb293..965a7c00 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -773,7 +773,7 @@ class TestNamespaces:
ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
- @pytest.yield_fixture
+ @pytest.fixture
def symlinked_tmpdir(self, tmpdir):
"""
Where available, return the tempdir as a symlink,
@@ -791,7 +791,7 @@ class TestNamespaces:
finally:
os.unlink(link_name)
- @pytest.yield_fixture(autouse=True)
+ @pytest.fixture(autouse=True)
def patched_path(self, tmpdir):
"""
Patch sys.path to include the 'site-pkgs' dir. Also
diff --git a/pyproject.toml b/pyproject.toml
index 2d362865..70e3473d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,30 @@
[build-system]
requires = [
- # avoid self install on Python 2; ref #1996
- "setuptools >= 40.8; python_version > '3'",
"wheel",
]
build-backend = "setuptools.build_meta"
backend-path = ["."]
+[tool.black]
+skip-string-normalization = true
+
+[tool.setuptools_scm]
+
+[pytest.enabler.black]
+#addopts = "--black"
+
+[pytest.enabler.mypy]
+#addopts = "--mypy"
+
+[pytest.enabler.flake8]
+addopts = "--flake8"
+
+[pytest.enabler.cov]
+addopts = "--cov"
+
+[pytest.enabler.xdist]
+addopts = "-n auto"
+
[tool.towncrier]
package = "setuptools"
package_dir = "setuptools"
@@ -41,9 +59,3 @@ backend-path = ["."]
directory = "misc"
name = "Misc"
showcontent = true
-
-[tool.jaraco.pytest.plugins.flake8]
-addopts = "--flake8"
-
-[tool.jaraco.pytest.plugins.cov]
-addopts = "--cov"
diff --git a/pytest.ini b/pytest.ini
index 162ad873..9aa1b173 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,8 +1,13 @@
[pytest]
-addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX
norecursedirs=dist build .tox .eggs
-doctest_optionflags=ELLIPSIS ALLOW_UNICODE
-filterwarnings =
+addopts=
+ --doctest-modules
+ --doctest-glob=pkg_resources/api_tests.txt
+ -r sxX
+doctest_optionflags=ALLOW_UNICODE ELLIPSIS
+# workaround for warning pytest-dev/pytest#6178
+junit_family=xunit2
+filterwarnings=
# Fail on warnings
error
# https://github.com/pypa/setuptools/issues/1823
diff --git a/runtime.txt b/runtime.txt
deleted file mode 100644
index 475ba515..00000000
--- a/runtime.txt
+++ /dev/null
@@ -1 +0,0 @@
-3.7
diff --git a/setup.cfg b/setup.cfg
index 5ee3a8e9..ebdc2c63 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,84 +1,92 @@
-[egg_info]
-tag_build = .post
-tag_date = 1
-
-[aliases]
-clean_egg_info = egg_info -Db ''
-release = clean_egg_info sdist bdist_wheel
-source = register sdist binary
-binary = bdist_egg upload --show-response
-
-[upload]
-repository = https://upload.pypi.org/legacy/
-
-[sdist]
-formats = zip
-
[metadata]
+license_files =
+ LICENSE
name = setuptools
-version = 50.3.2
-description = Easily download, build, install, upgrade, and uninstall Python packages
+version = 56.0.0
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
+description = Easily download, build, install, upgrade, and uninstall Python packages
+long_description = file:README.rst
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 :: 3
- Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.5
- Programming Language :: Python :: 3.6
- Programming Language :: Python :: 3.7
- Programming Language :: Python :: 3.8
- Programming Language :: Python :: 3.9
- Topic :: Software Development :: Libraries :: Python Modules
- Topic :: System :: Archiving :: Packaging
- Topic :: System :: Systems Administration
- Topic :: Utilities
+ Development Status :: 5 - Production/Stable
+ Intended Audience :: Developers
+ License :: OSI Approved :: MIT License
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3 :: Only
+ Topic :: Software Development :: Libraries :: Python Modules
+ Topic :: System :: Archiving :: Packaging
+ Topic :: System :: Systems Administration
+ Topic :: Utilities
+keywords = CPAN PyPI distutils eggs package management
+project_urls =
+ Documentation = https://setuptools.readthedocs.io/
[options]
-zip_safe = True
-python_requires = >=3.5
-py_modules = easy_install
-packages = find:
+packages = find_namespace:
+# disabled as it causes tests to be included #2505
+# include_package_data = true
+python_requires = >=3.6
+install_requires =
[options.packages.find]
-exclude = *.tests
+exclude =
+ build*
+ dist*
+ docs*
+ tests*
+ *.tests
+ tools*
[options.extras_require]
+testing =
+ # upstream
+ pytest >= 4.6
+ pytest-checkdocs >= 2.4
+ pytest-flake8
+ # python_implementation: workaround for jaraco/skeleton#22
+ # python_version: workaround for python/typed_ast#156
+ pytest-black >= 0.3.7; python_implementation != "PyPy" and python_version < "3.10"
+ pytest-cov
+ # python_implementation: workaround for jaraco/skeleton#22
+ # python_version: workaround for python/typed_ast#156
+ pytest-mypy; python_implementation != "PyPy" and python_version < "3.10"
+ pytest-enabler >= 1.0.1
+
+ # local
+ mock
+ flake8-2020
+ virtualenv>=13.0.0
+ pytest-virtualenv>=1.2.7
+ wheel
+ paver
+ pip>=19.1 # For proper file:// URLs support.
+ jaraco.envs
+ pytest-xdist
+ sphinx
+ jaraco.path>=3.2.0
+
+docs =
+ # upstream
+ sphinx
+ jaraco.packaging >= 8.2
+ rst.linker >= 1.9
+
+ # local
+ pygments-github-lexers==0.0.5
+ sphinx-inline-tabs
+
ssl =
- wincertstore==0.2; sys_platform=='win32'
+ wincertstore==0.2; sys_platform=='win32'
certs =
- certifi==2016.9.26
+ certifi==2016.9.26
-tests =
- mock
- pytest-flake8
- flake8-2020; python_version>="3.6"
- virtualenv>=13.0.0
- pytest-virtualenv>=1.2.7
- pytest>=3.7
- wheel
- coverage>=4.5.1
- # Coverage is unbearably slow on PyPy
- pytest-cov>=2.5.1; python_implementation != "PyPy"
- paver; python_version>="3.6"
- pip>=19.1 # For proper file:// URLs support.
- jaraco.envs
- jaraco.test >= 3.1.1; python_version >= "3.6"
+[options.entry_points]
-docs =
- # Keep these in sync with docs/requirements.txt
- sphinx
- jaraco.packaging>=6.1
- rst.linker>=1.9
- pygments-github-lexers==0.0.5
+[egg_info]
+tag_build = .post
+tag_date = 1
+
+[sdist]
+formats = zip
diff --git a/setup.py b/setup.py
index 2bd48daa..f5cbff31 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,4 @@
#!/usr/bin/env python
-"""
-Distutils setup file, used to install or test 'setuptools'
-"""
import os
import sys
@@ -13,17 +10,6 @@ from setuptools.command.install import install
here = os.path.dirname(__file__)
-def require_metadata():
- "Prevent improper installs without necessary metadata. See #659"
- egg_info_dir = os.path.join(here, 'setuptools.egg-info')
- if not os.path.exists(egg_info_dir):
- msg = (
- "Cannot build setuptools without metadata. "
- "Run `bootstrap.py`."
- )
- raise RuntimeError(msg)
-
-
def read_commands():
command_ns = {}
cmd_module_path = 'setuptools/command/__init__.py'
@@ -33,22 +19,6 @@ def read_commands():
return command_ns['__all__']
-def _gen_console_scripts():
- yield "easy_install = setuptools.command.easy_install:main"
-
- # Gentoo distributions manage the python-version-specific scripts
- # themselves, so those platforms define an environment variable to
- # suppress the creation of the version-specific scripts.
- var_names = (
- 'SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT',
- 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT',
- )
- 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='{}.{}'.format(*sys.version_info))
-
-
package_data = dict(
setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'],
)
@@ -173,9 +143,6 @@ setup_params = dict(
"depends.txt = setuptools.command.egg_info:warn_depends_obsolete",
"dependency_links.txt = setuptools.command.egg_info:overwrite_arg",
],
- "console_scripts": list(_gen_console_scripts()),
- "setuptools.installation":
- ['eggsecutable = setuptools.command.easy_install:bootstrap'],
},
dependency_links=[
pypi_link(
@@ -192,5 +159,4 @@ setup_params = dict(
if __name__ == '__main__':
# allow setup.py to run from another directory
here and os.chdir(here)
- require_metadata()
dist = setuptools.setup(**setup_params)
diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py
index 0ce190b8..0f702848 100644
--- a/setuptools/archive_util.py
+++ b/setuptools/archive_util.py
@@ -125,6 +125,56 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
os.chmod(target, unix_attributes)
+def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):
+ """Resolve any links and extract link targets as normal files."""
+ while tar_member_obj is not None and (
+ tar_member_obj.islnk() or tar_member_obj.issym()):
+ linkpath = tar_member_obj.linkname
+ if tar_member_obj.issym():
+ base = posixpath.dirname(tar_member_obj.name)
+ linkpath = posixpath.join(base, linkpath)
+ linkpath = posixpath.normpath(linkpath)
+ tar_member_obj = tar_obj._getmember(linkpath)
+
+ is_file_or_dir = (
+ tar_member_obj is not None and
+ (tar_member_obj.isfile() or tar_member_obj.isdir())
+ )
+ if is_file_or_dir:
+ return tar_member_obj
+
+ raise LookupError('Got unknown file type')
+
+
+def _iter_open_tar(tar_obj, extract_dir, progress_filter):
+ """Emit member-destination pairs from a tar archive."""
+ # don't do any chowning!
+ tar_obj.chown = lambda *args: None
+
+ with contextlib.closing(tar_obj):
+ for member in tar_obj:
+ name = member.name
+ # don't extract absolute paths or ones with .. in them
+ if name.startswith('/') or '..' in name.split('/'):
+ continue
+
+ prelim_dst = os.path.join(extract_dir, *name.split('/'))
+
+ try:
+ member = _resolve_tar_file_or_dir(tar_obj, member)
+ except LookupError:
+ continue
+
+ final_dst = progress_filter(name, prelim_dst)
+ if not final_dst:
+ continue
+
+ if final_dst.endswith(os.sep):
+ final_dst = final_dst[:-1]
+
+ yield member, final_dst
+
+
def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
"""Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
@@ -138,38 +188,18 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
raise UnrecognizedFormat(
"%s is not a compressed or uncompressed tar file" % (filename,)
) from e
- with contextlib.closing(tarobj):
- # don't do any chowning!
- tarobj.chown = lambda *args: None
- for member in tarobj:
- name = member.name
- # don't extract absolute paths or ones with .. in them
- if not name.startswith('/') and '..' not in name.split('/'):
- prelim_dst = os.path.join(extract_dir, *name.split('/'))
-
- # resolve any links and to extract the link targets as normal
- # files
- while member is not None and (
- member.islnk() or member.issym()):
- linkpath = member.linkname
- if member.issym():
- base = posixpath.dirname(member.name)
- linkpath = posixpath.join(base, linkpath)
- linkpath = posixpath.normpath(linkpath)
- member = tarobj._getmember(linkpath)
-
- if member is not None and (member.isfile() or member.isdir()):
- final_dst = progress_filter(name, prelim_dst)
- if final_dst:
- if final_dst.endswith(os.sep):
- final_dst = final_dst[:-1]
- try:
- # XXX Ugh
- tarobj._extract_member(member, final_dst)
- except tarfile.ExtractError:
- # chown/chmod/mkfifo/mknode/makedev failed
- pass
- return True
+
+ for member, final_dst in _iter_open_tar(
+ tarobj, extract_dir, progress_filter,
+ ):
+ try:
+ # XXX Ugh
+ tarobj._extract_member(member, final_dst)
+ except tarfile.ExtractError:
+ # chown/chmod/mkfifo/mknode/makedev failed
+ pass
+
+ return True
extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
index b9e8a2b3..9dfb2f24 100644
--- a/setuptools/build_meta.py
+++ b/setuptools/build_meta.py
@@ -101,7 +101,12 @@ def _file_with_extension(directory, extension):
f for f in os.listdir(directory)
if f.endswith(extension)
)
- file, = matching
+ try:
+ file, = matching
+ except ValueError:
+ raise ValueError(
+ 'No distribution was found. Ensure that `setup.py` '
+ 'is not empty and that it calls `setup()`.')
return file
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index 743f5588..570e6957 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -2,7 +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',
- 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info',
+ '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 a88efb45..e6b1609f 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -2,7 +2,6 @@
Build .egg distributions"""
-from distutils.errors import DistutilsSetupError
from distutils.dir_util import remove_tree, mkpath
from distutils import log
from types import CodeType
@@ -11,12 +10,10 @@ import os
import re
import textwrap
import marshal
-import warnings
from pkg_resources import get_build_platform, Distribution, ensure_directory
-from pkg_resources import EntryPoint
from setuptools.extension import Library
-from setuptools import Command, SetuptoolsDeprecationWarning
+from setuptools import Command
from sysconfig import get_path, get_python_version
@@ -153,7 +150,7 @@ class bdist_egg(Command):
self.run_command(cmdname)
return cmd
- def run(self):
+ def run(self): # noqa: C901 # is too complex (14) # FIXME
# Generate metadata first
self.run_command("egg_info")
# We run install_lib before install_data, because some data hacks
@@ -268,49 +265,7 @@ class bdist_egg(Command):
return analyze_egg(self.bdist_dir, self.stubs)
def gen_header(self):
- epm = EntryPoint.parse_map(self.distribution.entry_points or '')
- ep = epm.get('setuptools.installation', {}).get('eggsecutable')
- if ep is None:
- return 'w' # not an eggsecutable, do it the usual way.
-
- warnings.warn(
- "Eggsecutables are deprecated and will be removed in a future "
- "version.",
- SetuptoolsDeprecationWarning
- )
-
- if not ep.attrs or ep.extras:
- raise DistutilsSetupError(
- "eggsecutable entry point (%r) cannot have 'extras' "
- "or refer to a module" % (ep,)
- )
-
- pyver = '{}.{}'.format(*sys.version_info)
- pkg = ep.module_name
- full = '.'.join(ep.attrs)
- base = ep.attrs[0]
- basename = os.path.basename(self.egg_output)
-
- header = (
- "#!/bin/sh\n"
- 'if [ `basename $0` = "%(basename)s" ]\n'
- 'then exec python%(pyver)s -c "'
- "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
- "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
- '" "$@"\n'
- 'else\n'
- ' echo $0 is not the correct name for this egg file.\n'
- ' echo Please rename it back to %(basename)s and try again.\n'
- ' exec false\n'
- 'fi\n'
- ) % locals()
-
- if not self.dry_run:
- mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
- f = open(self.egg_output, 'w')
- f.write(header)
- f.close()
- return 'a'
+ return 'w'
def copy_metadata_to(self, target_dir):
"Copy metadata (egg info) to the target_dir"
diff --git a/setuptools/command/bdist_wininst.py b/setuptools/command/bdist_wininst.py
deleted file mode 100644
index ff4b6345..00000000
--- a/setuptools/command/bdist_wininst.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import distutils.command.bdist_wininst as orig
-import warnings
-
-from setuptools import SetuptoolsDeprecationWarning
-
-
-class bdist_wininst(orig.bdist_wininst):
- def reinitialize_command(self, command, reinit_subcommands=0):
- """
- Supplement reinitialize_command to work around
- http://bugs.python.org/issue20819
- """
- cmd = self.distribution.reinitialize_command(
- command, reinit_subcommands)
- if command in ('install', 'install_lib'):
- cmd.install_lib = None
- return cmd
-
- def run(self):
- warnings.warn(
- "bdist_wininst is deprecated and will be removed in a future "
- "version. Use bdist_wheel (wheel packages) instead.",
- SetuptoolsDeprecationWarning
- )
-
- self._is_running = True
- try:
- orig.bdist_wininst.run(self)
- finally:
- self._is_running = False
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 9ec83b7d..45adb6a1 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -67,7 +67,7 @@ warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
__all__ = [
'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
- 'main', 'get_exe_prefixes',
+ 'get_exe_prefixes',
]
@@ -226,7 +226,7 @@ class easy_install(Command):
print(tmpl.format(**locals()))
raise SystemExit()
- def finalize_options(self):
+ def finalize_options(self): # noqa: C901 # is too complex (25) # FIXME
self.version and self._render_version()
py_version = sys.version.split()[0]
@@ -437,7 +437,7 @@ class easy_install(Command):
def warn_deprecated_options(self):
pass
- def check_site_dir(self):
+ def check_site_dir(self): # noqa: C901 # is too complex (12) # FIXME
"""Verify that self.install_dir is .pth-capable dir, if needed"""
instdir = normalize_path(self.install_dir)
@@ -713,7 +713,10 @@ class easy_install(Command):
if getattr(self, attrname) is None:
setattr(self, attrname, scheme[key])
- def process_distribution(self, requirement, dist, deps=True, *info):
+ # FIXME: 'easy_install.process_distribution' is too complex (12)
+ def process_distribution( # noqa: C901
+ self, requirement, dist, deps=True, *info,
+ ):
self.update_pth(dist)
self.package_index.add(dist)
if dist in self.local_index[dist.key]:
@@ -837,12 +840,19 @@ class easy_install(Command):
def install_eggs(self, spec, dist_filename, tmpdir):
# .egg dirs or files are already built, so just return them
- if dist_filename.lower().endswith('.egg'):
- return [self.install_egg(dist_filename, tmpdir)]
- elif dist_filename.lower().endswith('.exe'):
- return [self.install_exe(dist_filename, tmpdir)]
- elif dist_filename.lower().endswith('.whl'):
- return [self.install_wheel(dist_filename, tmpdir)]
+ installer_map = {
+ '.egg': self.install_egg,
+ '.exe': self.install_exe,
+ '.whl': self.install_wheel,
+ }
+ try:
+ install_dist = installer_map[
+ dist_filename.lower()[-4:]
+ ]
+ except KeyError:
+ pass
+ else:
+ return [install_dist(dist_filename, tmpdir)]
# Anything else, try to extract and build
setup_base = tmpdir
@@ -887,7 +897,8 @@ class easy_install(Command):
metadata = EggMetadata(zipimport.zipimporter(egg_path))
return Distribution.from_filename(egg_path, metadata=metadata)
- def install_egg(self, egg_path, tmpdir):
+ # FIXME: 'easy_install.install_egg' is too complex (11)
+ def install_egg(self, egg_path, tmpdir): # noqa: C901
destination = os.path.join(
self.install_dir,
os.path.basename(egg_path),
@@ -986,7 +997,8 @@ class easy_install(Command):
# install the .egg
return self.install_egg(egg_path, tmpdir)
- def exe_to_egg(self, dist_filename, egg_tmp):
+ # FIXME: 'easy_install.exe_to_egg' is too complex (12)
+ def exe_to_egg(self, dist_filename, egg_tmp): # noqa: C901
"""Extract a bdist_wininst to the directories an egg would use"""
# Check for .pth file and set up prefix translations
prefixes = get_exe_prefixes(dist_filename)
@@ -1178,22 +1190,24 @@ class easy_install(Command):
for key, val in ei_opts.items():
if key not in fetch_directives:
continue
- fetch_options[key.replace('_', '-')] = val[1]
+ fetch_options[key] = val[1]
# create a settings dictionary suitable for `edit_config`
settings = dict(easy_install=fetch_options)
cfg_filename = os.path.join(base, 'setup.cfg')
setopt.edit_config(cfg_filename, settings)
- def update_pth(self, dist):
+ def update_pth(self, dist): # noqa: C901 # is too complex (11) # FIXME
if self.pth_file is None:
return
for d in self.pth_file[dist.key]: # drop old entries
- if self.multi_version or d.location != dist.location:
- log.info("Removing %s from easy-install.pth file", d)
- self.pth_file.remove(d)
- if d.location in self.shadow_path:
- self.shadow_path.remove(d.location)
+ if not self.multi_version and d.location == dist.location:
+ continue
+
+ log.info("Removing %s from easy-install.pth file", d)
+ self.pth_file.remove(d)
+ if d.location in self.shadow_path:
+ self.shadow_path.remove(d.location)
if not self.multi_version:
if dist.location in self.pth_file.paths:
@@ -1207,19 +1221,21 @@ class easy_install(Command):
if dist.location not in self.shadow_path:
self.shadow_path.append(dist.location)
- if not self.dry_run:
+ if self.dry_run:
+ return
- self.pth_file.save()
+ self.pth_file.save()
- if dist.key == 'setuptools':
- # Ensure that setuptools itself never becomes unavailable!
- # XXX should this check for latest version?
- filename = os.path.join(self.install_dir, 'setuptools.pth')
- if os.path.islink(filename):
- os.unlink(filename)
- f = open(filename, 'wt')
- f.write(self.pth_file.make_relative(dist.location) + '\n')
- f.close()
+ if dist.key != 'setuptools':
+ return
+
+ # Ensure that setuptools itself never becomes unavailable!
+ # XXX should this check for latest version?
+ filename = os.path.join(self.install_dir, 'setuptools.pth')
+ if os.path.islink(filename):
+ os.unlink(filename)
+ with open(filename, 'wt') as f:
+ f.write(self.pth_file.make_relative(dist.location) + '\n')
def unpack_progress(self, src, dst):
# Progress filter for unpacking
@@ -1360,58 +1376,63 @@ def get_site_dirs():
if sys.exec_prefix != sys.prefix:
prefixes.append(sys.exec_prefix)
for prefix in prefixes:
- if prefix:
- if sys.platform in ('os2emx', 'riscos'):
- sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
- elif os.sep == '/':
- sitedirs.extend([
- os.path.join(
- prefix,
- "lib",
- "python{}.{}".format(*sys.version_info),
- "site-packages",
- ),
- os.path.join(prefix, "lib", "site-python"),
- ])
- else:
- sitedirs.extend([
+ if not prefix:
+ continue
+
+ if sys.platform in ('os2emx', 'riscos'):
+ sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+ elif os.sep == '/':
+ sitedirs.extend([
+ os.path.join(
prefix,
- os.path.join(prefix, "lib", "site-packages"),
- ])
- if sys.platform == 'darwin':
- # for framework builds *only* we add the standard Apple
- # locations. Currently only per-user, but /Library and
- # /Network/Library could be added too
- if 'Python.framework' in prefix:
- home = os.environ.get('HOME')
- if home:
- home_sp = os.path.join(
- home,
- 'Library',
- 'Python',
- '{}.{}'.format(*sys.version_info),
- 'site-packages',
- )
- sitedirs.append(home_sp)
+ "lib",
+ "python{}.{}".format(*sys.version_info),
+ "site-packages",
+ ),
+ os.path.join(prefix, "lib", "site-python"),
+ ])
+ else:
+ sitedirs.extend([
+ prefix,
+ os.path.join(prefix, "lib", "site-packages"),
+ ])
+ if sys.platform != 'darwin':
+ continue
+
+ # for framework builds *only* we add the standard Apple
+ # locations. Currently only per-user, but /Library and
+ # /Network/Library could be added too
+ if 'Python.framework' not in prefix:
+ continue
+
+ home = os.environ.get('HOME')
+ if not home:
+ continue
+
+ home_sp = os.path.join(
+ home,
+ 'Library',
+ 'Python',
+ '{}.{}'.format(*sys.version_info),
+ 'site-packages',
+ )
+ sitedirs.append(home_sp)
lib_paths = get_path('purelib'), get_path('platlib')
- for site_lib in lib_paths:
- if site_lib not in sitedirs:
- sitedirs.append(site_lib)
+
+ sitedirs.extend(s for s in lib_paths if s not in sitedirs)
if site.ENABLE_USER_SITE:
sitedirs.append(site.USER_SITE)
- try:
+ with contextlib.suppress(AttributeError):
sitedirs.extend(site.getsitepackages())
- except AttributeError:
- pass
sitedirs = list(map(normalize_path, sitedirs))
return sitedirs
-def expand_paths(inputs):
+def expand_paths(inputs): # noqa: C901 # is too complex (11) # FIXME
"""Yield sys.path directories that might contain "old-style" packages"""
seen = {}
@@ -1443,13 +1464,18 @@ def expand_paths(inputs):
# Yield existing non-dupe, non-import directory lines from it
for line in lines:
- if not line.startswith("import"):
- line = normalize_path(line.rstrip())
- if line not in seen:
- seen[line] = 1
- if not os.path.isdir(line):
- continue
- yield line, os.listdir(line)
+ if line.startswith("import"):
+ continue
+
+ line = normalize_path(line.rstrip())
+ if line in seen:
+ continue
+
+ seen[line] = 1
+ if not os.path.isdir(line):
+ continue
+
+ yield line, os.listdir(line)
def extract_wininst_cfg(dist_filename):
@@ -2167,7 +2193,7 @@ class WindowsScriptWriter(ScriptWriter):
@classmethod
def _adjust_header(cls, type_, orig_header):
"""
- Make sure 'pythonw' is used for gui and and 'python' is used for
+ Make sure 'pythonw' is used for gui and 'python' is used for
console (regardless of what sys.executable is).
"""
pattern = 'pythonw.exe'
@@ -2258,60 +2284,6 @@ def current_umask():
return tmp
-def bootstrap():
- # This function is called when setuptools*.egg is run using /bin/sh
- import setuptools
-
- argv0 = os.path.dirname(setuptools.__path__[0])
- sys.argv[0] = argv0
- sys.argv.append(argv0)
- main()
-
-
-def main(argv=None, **kw):
- from setuptools import setup
- from setuptools.dist import Distribution
-
- class DistributionWithoutHelpCommands(Distribution):
- common_usage = ""
-
- def _show_help(self, *args, **kw):
- with _patch_usage():
- Distribution._show_help(self, *args, **kw)
-
- if argv is None:
- argv = sys.argv[1:]
-
- with _patch_usage():
- setup(
- script_args=['-q', 'easy_install', '-v'] + argv,
- script_name=sys.argv[0] or 'easy_install',
- distclass=DistributionWithoutHelpCommands,
- **kw
- )
-
-
-@contextlib.contextmanager
-def _patch_usage():
- import distutils.core
- USAGE = textwrap.dedent("""
- usage: %(script)s [options] requirement_or_url ...
- or: %(script)s --help
- """).lstrip()
-
- def gen_usage(script_name):
- return USAGE % dict(
- script=os.path.basename(script_name),
- )
-
- saved = distutils.core.gen_usage
- distutils.core.gen_usage = gen_usage
- try:
- yield
- finally:
- distutils.core.gen_usage = saved
-
-
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
"""
Warning for EasyInstall deprecations, bypassing suppression.
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 0b7ad677..1f120b67 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -8,6 +8,7 @@ from distutils.util import convert_path
from distutils import log
import distutils.errors
import distutils.filelist
+import functools
import os
import re
import sys
@@ -31,7 +32,7 @@ from setuptools.extern import packaging
from setuptools import SetuptoolsDeprecationWarning
-def translate_pattern(glob):
+def translate_pattern(glob): # noqa: C901 # is too complex (14) # FIXME
"""
Translate a file path glob like '*.txt' in to a regular expression.
This differs from fnmatch.translate which allows wildcards to match
@@ -332,70 +333,74 @@ class FileList(_FileList):
# patterns, (dir and patterns), or (dir_pattern).
(action, patterns, dir, dir_pattern) = self._parse_template_line(line)
+ action_map = {
+ 'include': self.include,
+ 'exclude': self.exclude,
+ 'global-include': self.global_include,
+ 'global-exclude': self.global_exclude,
+ 'recursive-include': functools.partial(
+ self.recursive_include, dir,
+ ),
+ 'recursive-exclude': functools.partial(
+ self.recursive_exclude, dir,
+ ),
+ 'graft': self.graft,
+ 'prune': self.prune,
+ }
+ log_map = {
+ 'include': "warning: no files found matching '%s'",
+ 'exclude': (
+ "warning: no previously-included files found "
+ "matching '%s'"
+ ),
+ 'global-include': (
+ "warning: no files found matching '%s' "
+ "anywhere in distribution"
+ ),
+ 'global-exclude': (
+ "warning: no previously-included files matching "
+ "'%s' found anywhere in distribution"
+ ),
+ 'recursive-include': (
+ "warning: no files found matching '%s' "
+ "under directory '%s'"
+ ),
+ 'recursive-exclude': (
+ "warning: no previously-included files matching "
+ "'%s' found under directory '%s'"
+ ),
+ 'graft': "warning: no directories found matching '%s'",
+ 'prune': "no previously-included directories found matching '%s'",
+ }
+
+ try:
+ process_action = action_map[action]
+ except KeyError:
+ raise DistutilsInternalError(
+ "this cannot happen: invalid action '{action!s}'".
+ format(action=action),
+ )
+
# OK, now we know that the action is valid and we have the
# right number of words on the line for that action -- so we
# can proceed with minimal error-checking.
- if action == 'include':
- self.debug_print("include " + ' '.join(patterns))
- for pattern in patterns:
- if not self.include(pattern):
- log.warn("warning: no files found matching '%s'", pattern)
-
- elif action == 'exclude':
- self.debug_print("exclude " + ' '.join(patterns))
- for pattern in patterns:
- if not self.exclude(pattern):
- log.warn(("warning: no previously-included files "
- "found matching '%s'"), pattern)
-
- elif action == 'global-include':
- self.debug_print("global-include " + ' '.join(patterns))
- for pattern in patterns:
- if not self.global_include(pattern):
- log.warn(("warning: no files found matching '%s' "
- "anywhere in distribution"), pattern)
-
- elif action == 'global-exclude':
- self.debug_print("global-exclude " + ' '.join(patterns))
- for pattern in patterns:
- if not self.global_exclude(pattern):
- log.warn(("warning: no previously-included files matching "
- "'%s' found anywhere in distribution"),
- pattern)
-
- elif action == 'recursive-include':
- self.debug_print("recursive-include %s %s" %
- (dir, ' '.join(patterns)))
- for pattern in patterns:
- if not self.recursive_include(dir, pattern):
- log.warn(("warning: no files found matching '%s' "
- "under directory '%s'"),
- pattern, dir)
-
- elif action == 'recursive-exclude':
- self.debug_print("recursive-exclude %s %s" %
- (dir, ' '.join(patterns)))
- for pattern in patterns:
- if not self.recursive_exclude(dir, pattern):
- log.warn(("warning: no previously-included files matching "
- "'%s' found under directory '%s'"),
- pattern, dir)
-
- elif action == 'graft':
- self.debug_print("graft " + dir_pattern)
- if not self.graft(dir_pattern):
- log.warn("warning: no directories found matching '%s'",
- dir_pattern)
-
- elif action == 'prune':
- self.debug_print("prune " + dir_pattern)
- if not self.prune(dir_pattern):
- log.warn(("no previously-included directories found "
- "matching '%s'"), dir_pattern)
-
- else:
- raise DistutilsInternalError(
- "this cannot happen: invalid action '%s'" % action)
+
+ action_is_recursive = action.startswith('recursive-')
+ if action in {'graft', 'prune'}:
+ patterns = [dir_pattern]
+ extra_log_args = (dir, ) if action_is_recursive else ()
+ log_tmpl = log_map[action]
+
+ self.debug_print(
+ ' '.join(
+ [action] +
+ ([dir] if action_is_recursive else []) +
+ patterns,
+ )
+ )
+ for pattern in patterns:
+ if not process_action(pattern):
+ log.warn(log_tmpl, pattern, *extra_log_args)
def _remove_files(self, predicate):
"""
diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py
index 8c9a15e2..9cd8eb06 100644
--- a/setuptools/command/install_scripts.py
+++ b/setuptools/command/install_scripts.py
@@ -1,5 +1,6 @@
from distutils import log
import distutils.command.install_scripts as orig
+from distutils.errors import DistutilsModuleError
import os
import sys
@@ -35,7 +36,7 @@ class install_scripts(orig.install_scripts):
try:
bw_cmd = self.get_finalized_command("bdist_wininst")
is_wininst = getattr(bw_cmd, '_is_running', False)
- except ImportError:
+ except (ImportError, DistutilsModuleError):
is_wininst = False
writer = ei.ScriptWriter
if is_wininst:
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index 887b7efa..a6ea814a 100644
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -4,6 +4,7 @@ import os
import sys
import io
import contextlib
+from glob import iglob
from setuptools.extern import ordered_set
@@ -194,29 +195,41 @@ class sdist(sdist_add_defaults, orig.sdist):
"""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)
-
+ files = ordered_set.OrderedSet()
try:
- files.update(self.distribution.metadata.license_files)
+ license_files = 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)
+ license_files = ordered_set.OrderedSet()
+ patterns = license_files if isinstance(license_files, ordered_set.OrderedSet) \
+ else ordered_set.OrderedSet(license_files)
+
+ if 'license_file' in opts:
+ log.warn(
+ "warning: the 'license_file' option is deprecated, "
+ "use 'license_files' instead")
+ patterns.append(opts['license_file'][1])
+
+ if 'license_file' not in opts and 'license_files' not in opts:
+ # Default patterns match the ones wheel uses
+ # See https://wheel.readthedocs.io/en/stable/user_guide.html
+ # -> 'Including license files in the generated wheel file'
+ patterns = ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
+
+ for pattern in patterns:
+ for path in iglob(pattern):
+ if path.endswith('~'):
+ log.debug(
+ "ignoring license file '%s' as it looks like a backup",
+ path)
+ continue
+
+ if path not in files and os.path.isfile(path):
+ log.info(
+ "adding license file '%s' (matched pattern '%s')",
+ path, pattern)
+ files.add(path)
+
+ self.filelist.extend(sorted(files))
diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py
index 2559458a..845bff44 100644
--- a/setuptools/command/upload_docs.py
+++ b/setuptools/command/upload_docs.py
@@ -2,7 +2,7 @@
"""upload_docs
Implements a Distutils 'upload_docs' subcommand (upload documentation to
-PyPI's pythonhosted.org).
+sites other than PyPi such as devpi).
"""
from base64 import standard_b64encode
@@ -31,7 +31,7 @@ class upload_docs(upload):
# supported by Warehouse (and won't be).
DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/'
- description = 'Upload documentation to PyPI'
+ description = 'Upload documentation to sites other than PyPi such as devpi'
user_options = [
('repository=', 'r',
@@ -59,7 +59,7 @@ class upload_docs(upload):
if self.upload_dir is None:
if self.has_sphinx():
build_sphinx = self.get_finalized_command('build_sphinx')
- self.target_dir = build_sphinx.builder_target_dir
+ self.target_dir = dict(build_sphinx.builder_target_dirs)['html']
else:
build = self.get_finalized_command('build')
self.target_dir = os.path.join(build.build_base, 'docs')
@@ -67,7 +67,7 @@ class upload_docs(upload):
self.ensure_dirname('upload_dir')
self.target_dir = self.upload_dir
if 'pypi.python.org' in self.repository:
- log.warn("Upload_docs command is deprecated. Use RTD instead.")
+ log.warn("Upload_docs command is deprecated for PyPi. Use RTD instead.")
self.announce('Using upload directory %s' % self.target_dir)
def create_zipfile(self, filename):
diff --git a/setuptools/config.py b/setuptools/config.py
index af3a3bcb..4a6cd469 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -574,6 +574,7 @@ class ConfigOptionsHandler(ConfigHandler):
parse_list_semicolon = partial(self._parse_list, separator=';')
parse_bool = self._parse_bool
parse_dict = self._parse_dict
+ parse_cmdclass = self._parse_cmdclass
return {
'zip_safe': parse_bool,
@@ -594,6 +595,22 @@ class ConfigOptionsHandler(ConfigHandler):
'entry_points': self._parse_file,
'py_modules': parse_list,
'python_requires': SpecifierSet,
+ 'cmdclass': parse_cmdclass,
+ }
+
+ def _parse_cmdclass(self, value):
+ def resolve_class(qualified_class_name):
+ idx = qualified_class_name.rfind('.')
+ class_name = qualified_class_name[idx+1:]
+ pkg_name = qualified_class_name[:idx]
+
+ module = __import__(pkg_name)
+
+ return getattr(module, class_name)
+
+ return {
+ k: resolve_class(v)
+ for k, v in self._parse_dict(value).items()
}
def _parse_packages(self, value):
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 2c088ef8..c7af35dc 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -11,10 +11,13 @@ import distutils.log
import distutils.core
import distutils.cmd
import distutils.dist
+import distutils.command
from distutils.util import strtobool
from distutils.debug import DEBUG
from distutils.fancy_getopt import translate_longopt
import itertools
+import textwrap
+from typing import List, Optional, TYPE_CHECKING
from collections import defaultdict
from email import message_from_file
@@ -29,11 +32,15 @@ from setuptools.extern import ordered_set
from . import SetuptoolsDeprecationWarning
import setuptools
+import setuptools.command
from setuptools import windows_support
from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration
import pkg_resources
+if TYPE_CHECKING:
+ from email.message import Message
+
__import__('setuptools.extern.packaging.specifiers')
__import__('setuptools.extern.packaging.version')
@@ -65,61 +72,92 @@ def get_metadata_version(self):
return mv
-def read_pkg_file(self, file):
- """Reads the metadata values from a file object."""
- msg = message_from_file(file)
+def rfc822_unescape(content: str) -> str:
+ """Reverse RFC-822 escaping by removing leading whitespaces from content."""
+ lines = content.splitlines()
+ if len(lines) == 1:
+ return lines[0].lstrip()
+ return '\n'.join(
+ (lines[0].lstrip(),
+ textwrap.dedent('\n'.join(lines[1:]))))
+
+
+def _read_field_from_msg(msg: "Message", field: str) -> Optional[str]:
+ """Read Message header field."""
+ value = msg[field]
+ if value == 'UNKNOWN':
+ return None
+ return value
+
- def _read_field(name):
- value = msg[name]
- if value == 'UNKNOWN':
- return None
+def _read_field_unescaped_from_msg(msg: "Message", field: str) -> Optional[str]:
+ """Read Message header field and apply rfc822_unescape."""
+ value = _read_field_from_msg(msg, field)
+ if value is None:
return value
+ return rfc822_unescape(value)
- def _read_list(name):
- values = msg.get_all(name, None)
- if values == []:
- return None
- return values
+
+def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]:
+ """Read Message header field and return all results as list."""
+ values = msg.get_all(field, None)
+ if values == []:
+ return None
+ return values
+
+
+def read_pkg_file(self, file):
+ """Reads the metadata values from a file object."""
+ msg = message_from_file(file)
self.metadata_version = StrictVersion(msg['metadata-version'])
- self.name = _read_field('name')
- self.version = _read_field('version')
- self.description = _read_field('summary')
+ self.name = _read_field_from_msg(msg, 'name')
+ self.version = _read_field_from_msg(msg, 'version')
+ self.description = _read_field_from_msg(msg, 'summary')
# we are filling author only.
- self.author = _read_field('author')
+ self.author = _read_field_from_msg(msg, 'author')
self.maintainer = None
- self.author_email = _read_field('author-email')
+ self.author_email = _read_field_from_msg(msg, 'author-email')
self.maintainer_email = None
- self.url = _read_field('home-page')
- self.license = _read_field('license')
+ self.url = _read_field_from_msg(msg, 'home-page')
+ self.license = _read_field_from_msg(msg, 'license')
if 'download-url' in msg:
- self.download_url = _read_field('download-url')
+ self.download_url = _read_field_from_msg(msg, 'download-url')
else:
self.download_url = None
- self.long_description = _read_field('description')
- self.description = _read_field('summary')
+ self.long_description = _read_field_unescaped_from_msg(msg, 'description')
+ self.description = _read_field_from_msg(msg, 'summary')
if 'keywords' in msg:
- self.keywords = _read_field('keywords').split(',')
+ self.keywords = _read_field_from_msg(msg, 'keywords').split(',')
- self.platforms = _read_list('platform')
- self.classifiers = _read_list('classifier')
+ self.platforms = _read_list_from_msg(msg, 'platform')
+ self.classifiers = _read_list_from_msg(msg, 'classifier')
# PEP 314 - these fields only exist in 1.1
if self.metadata_version == StrictVersion('1.1'):
- self.requires = _read_list('requires')
- self.provides = _read_list('provides')
- self.obsoletes = _read_list('obsoletes')
+ self.requires = _read_list_from_msg(msg, 'requires')
+ self.provides = _read_list_from_msg(msg, 'provides')
+ self.obsoletes = _read_list_from_msg(msg, 'obsoletes')
else:
self.requires = None
self.provides = None
self.obsoletes = None
+def single_line(val):
+ # quick and dirty validation for description pypa/setuptools#1390
+ if '\n' in val:
+ # TODO after 2021-07-31: Replace with `raise ValueError("newlines not allowed")`
+ warnings.warn("newlines not allowed and will break in the future")
+ val = val.replace('\n', ' ')
+ return val
+
+
# Based on Python 3.5 version
-def write_pkg_file(self, file):
+def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
"""Write the PKG-INFO format data to a file object.
"""
version = self.get_metadata_version()
@@ -130,7 +168,7 @@ def write_pkg_file(self, file):
write_field('Metadata-Version', str(version))
write_field('Name', self.get_name())
write_field('Version', self.get_version())
- write_field('Summary', self.get_description())
+ write_field('Summary', single_line(self.get_description()))
write_field('Home-page', self.get_url())
if version < StrictVersion('1.2'):
@@ -283,7 +321,7 @@ def check_specifier(dist, attr, value):
"""Verify that value is a valid version specifier"""
try:
packaging.specifiers.SpecifierSet(value)
- except packaging.specifiers.InvalidSpecifier as error:
+ except (packaging.specifiers.InvalidSpecifier, AttributeError) as error:
tmpl = (
"{attr!r} must be a string "
"containing valid version specifiers; {error}"
@@ -548,7 +586,8 @@ class Distribution(_Distribution):
req.marker = None
return req
- def _parse_config_files(self, filenames=None):
+ # FIXME: 'Distribution._parse_config_files' is too complex (14)
+ def _parse_config_files(self, filenames=None): # noqa: C901
"""
Adapted from distutils.dist.Distribution.parse_config_files,
this method provides the same functionality in subtly-improved
@@ -557,14 +596,12 @@ class Distribution(_Distribution):
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 = [] if sys.prefix == sys.base_prefix else [
+ 'install-base', 'install-platbase', 'install-lib',
+ 'install-platlib', 'install-purelib', 'install-headers',
+ 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
+ 'home', 'user', 'root',
+ ]
ignore_options = frozenset(ignore_options)
@@ -575,6 +612,7 @@ class Distribution(_Distribution):
self.announce("Distribution.parse_config_files():")
parser = ConfigParser()
+ parser.optionxform = str
for filename in filenames:
with io.open(filename, encoding='utf-8') as reader:
if DEBUG:
@@ -585,32 +623,69 @@ class Distribution(_Distribution):
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)
+ if opt == '__name__' or opt in ignore_options:
+ continue
+
+ val = parser.get(section, opt)
+ opt = self.warn_dash_deprecation(opt, section)
+ opt = self.make_option_lowercase(opt, section)
+ opt_dict[opt] = (filename, val)
# Make the ConfigParser forget everything (so we retain
# the original filenames that options come from)
parser.__init__()
+ if 'global' not in self.command_options:
+ return
+
# 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 e:
- raise DistutilsOptionError(e) from e
+ for (opt, (src, val)) in self.command_options['global'].items():
+ alias = self.negative_opt.get(opt)
+ if alias:
+ val = not strtobool(val)
+ elif opt in ('verbose', 'dry_run'): # ugh!
+ val = strtobool(val)
+
+ try:
+ setattr(self, alias or opt, val)
+ except ValueError as e:
+ raise DistutilsOptionError(e) from e
+
+ def warn_dash_deprecation(self, opt, section):
+ if section in (
+ 'options.extras_require', 'options.data_files',
+ ):
+ return opt
+
+ underscore_opt = opt.replace('-', '_')
+ commands = distutils.command.__all__ + setuptools.command.__all__
+ if (not section.startswith('options') and section != 'metadata'
+ and section not in commands):
+ return underscore_opt
+
+ if '-' in opt:
+ warnings.warn(
+ "Usage of dash-separated '%s' will not be supported in future "
+ "versions. Please use the underscore name '%s' instead"
+ % (opt, underscore_opt))
+ return underscore_opt
+
+ def make_option_lowercase(self, opt, section):
+ if section != 'metadata' or opt.islower():
+ return opt
+
+ lowercase_opt = opt.lower()
+ warnings.warn(
+ "Usage of uppercase key '%s' in '%s' will be deprecated in future "
+ "versions. Please use lowercase '%s' instead"
+ % (opt, section, lowercase_opt)
+ )
+ return lowercase_opt
- def _set_command_options(self, command_obj, option_dict=None):
+ # FIXME: 'Distribution._set_command_options' is too complex (14)
+ def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
"""
Set the options for 'command_obj' from 'option_dict'. Basically
this means copying elements of a dictionary ('option_dict') to
diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py
index b7f30dc2..7df32fde 100644
--- a/setuptools/extern/__init__.py
+++ b/setuptools/extern/__init__.py
@@ -1,3 +1,4 @@
+import importlib.util
import sys
@@ -20,17 +21,10 @@ class VendorImporter:
yield self.vendor_pkg + '.'
yield ''
- def find_module(self, fullname, path=None):
- """
- Return self when fullname starts with root_name and the
- target module is one vendored through this importer.
- """
+ def _module_matches_namespace(self, fullname):
+ """Figure out if the target module is vendored."""
root, base, target = fullname.partition(self.root_name + '.')
- if root:
- return
- if not any(map(target.startswith, self.vendored_names)):
- return
- return self
+ return not root and any(map(target.startswith, self.vendored_names))
def load_module(self, fullname):
"""
@@ -54,6 +48,19 @@ class VendorImporter:
"distribution.".format(**locals())
)
+ def create_module(self, spec):
+ return self.load_module(spec.name)
+
+ def exec_module(self, module):
+ pass
+
+ def find_spec(self, fullname, path=None, target=None):
+ """Return a module spec for vendored names."""
+ return (
+ importlib.util.spec_from_loader(fullname, self)
+ if self._module_matches_namespace(fullname) else None
+ )
+
def install(self):
"""
Install this importer into sys.meta_path if not already present.
diff --git a/setuptools/glob.py b/setuptools/glob.py
index 9d7cbc5d..87062b81 100644
--- a/setuptools/glob.py
+++ b/setuptools/glob.py
@@ -47,6 +47,8 @@ def iglob(pathname, recursive=False):
def _iglob(pathname, recursive):
dirname, basename = os.path.split(pathname)
+ glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob1
+
if not has_magic(pathname):
if basename:
if os.path.lexists(pathname):
@@ -56,13 +58,9 @@ def _iglob(pathname, recursive):
if os.path.isdir(dirname):
yield pathname
return
+
if not dirname:
- if recursive and _isrecursive(basename):
- for x in glob2(dirname, basename):
- yield x
- else:
- for x in glob1(dirname, basename):
- yield x
+ yield from glob_in_dir(dirname, basename)
return
# `os.path.split()` returns the argument itself as a dirname if it is a
# drive or UNC path. Prevent an infinite recursion if a drive or UNC path
@@ -71,12 +69,7 @@ def _iglob(pathname, recursive):
dirs = _iglob(dirname, recursive)
else:
dirs = [dirname]
- if has_magic(basename):
- if recursive and _isrecursive(basename):
- glob_in_dir = glob2
- else:
- glob_in_dir = glob1
- else:
+ if not has_magic(basename):
glob_in_dir = glob0
for dirname in dirs:
for name in glob_in_dir(dirname, basename):
diff --git a/setuptools/installer.py b/setuptools/installer.py
index e630b874..57e2b587 100644
--- a/setuptools/installer.py
+++ b/setuptools/installer.py
@@ -7,7 +7,6 @@ from distutils import log
from distutils.errors import DistutilsError
import pkg_resources
-from setuptools.command.easy_install import easy_install
from setuptools.wheel import Wheel
@@ -19,54 +18,11 @@ def _fixup_find_links(find_links):
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):
+def fetch_build_egg(dist, req): # noqa: C901 # is too complex (16) # FIXME
"""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.
+ # Warn if wheel is not available
try:
pkg_resources.get_distribution('wheel')
except pkg_resources.DistributionNotFound:
@@ -80,20 +36,17 @@ def fetch_build_egg(dist, req):
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
+ quiet = 'PIP_QUIET' not in os.environ and 'PIP_VERBOSE' not in os.environ
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 = []
+ find_links = (
+ _fixup_find_links(opts['find_links'][1])[:] if 'find_links' in opts
+ else []
+ )
if dist.dependency_links:
find_links.extend(dist.dependency_links)
eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
@@ -112,16 +65,12 @@ def fetch_build_egg(dist, req):
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))
+ for link in find_links or []:
+ 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))
+ cmd.append(req.url or str(req))
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
diff --git a/setuptools/msvc.py b/setuptools/msvc.py
index 1ead72b4..d5e0a952 100644
--- a/setuptools/msvc.py
+++ b/setuptools/msvc.py
@@ -24,6 +24,7 @@ from io import open
from os import listdir, pathsep
from os.path import join, isfile, isdir, dirname
import sys
+import contextlib
import platform
import itertools
import subprocess
@@ -724,28 +725,23 @@ class SystemInfo:
ms = self.ri.microsoft
vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
vs_vers = []
- for hkey in self.ri.HKEYS:
- for key in vckeys:
- try:
- bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
- except (OSError, IOError):
- continue
- with bkey:
- subkeys, values, _ = winreg.QueryInfoKey(bkey)
- for i in range(values):
- try:
- ver = float(winreg.EnumValue(bkey, i)[0])
- 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 vs_vers:
- vs_vers.append(ver)
- except ValueError:
- pass
+ for hkey, key in itertools.product(self.ri.HKEYS, vckeys):
+ try:
+ bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)
+ except (OSError, IOError):
+ continue
+ with bkey:
+ subkeys, values, _ = winreg.QueryInfoKey(bkey)
+ for i in range(values):
+ with contextlib.suppress(ValueError):
+ ver = float(winreg.EnumValue(bkey, i)[0])
+ if ver not in vs_vers:
+ vs_vers.append(ver)
+ for i in range(subkeys):
+ with contextlib.suppress(ValueError):
+ ver = float(winreg.EnumKey(bkey, i))
+ if ver not in vs_vers:
+ vs_vers.append(ver)
return sorted(vs_vers)
def find_programdata_vs_vers(self):
@@ -925,8 +921,8 @@ class SystemInfo:
"""
return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
- @property
- def WindowsSdkDir(self):
+ @property # noqa: C901
+ def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME
"""
Microsoft Windows SDK directory.
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 3979b131..123e9582 100644
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -320,7 +320,8 @@ class PackageIndex(Environment):
else:
self.opener = urllib.request.urlopen
- def process_url(self, url, retrieve=False):
+ # FIXME: 'PackageIndex.process_url' is too complex (14)
+ def process_url(self, url, retrieve=False): # noqa: C901
"""Evaluate a URL as a possible download, and maybe retrieve it"""
if url in self.scanned_urls and not retrieve:
return
@@ -428,49 +429,53 @@ class PackageIndex(Environment):
dist.precedence = SOURCE_DIST
self.add(dist)
+ def _scan(self, link):
+ # Process a URL to see if it's for a package page
+ NO_MATCH_SENTINEL = None, None
+ if not link.startswith(self.index_url):
+ return NO_MATCH_SENTINEL
+
+ parts = list(map(
+ urllib.parse.unquote, link[len(self.index_url):].split('/')
+ ))
+ if len(parts) != 2 or '#' in parts[1]:
+ return NO_MATCH_SENTINEL
+
+ # it's a package page, sanitize and index it
+ pkg = safe_name(parts[0])
+ ver = safe_version(parts[1])
+ self.package_pages.setdefault(pkg.lower(), {})[link] = True
+ return to_filename(pkg), to_filename(ver)
+
def process_index(self, url, page):
"""Process the contents of a PyPI page"""
- def scan(link):
- # Process a URL to see if it's for a package page
- if link.startswith(self.index_url):
- parts = list(map(
- urllib.parse.unquote, link[len(self.index_url):].split('/')
- ))
- if len(parts) == 2 and '#' not in parts[1]:
- # it's a package page, sanitize and index it
- pkg = safe_name(parts[0])
- ver = safe_version(parts[1])
- self.package_pages.setdefault(pkg.lower(), {})[link] = True
- return to_filename(pkg), to_filename(ver)
- return None, None
-
# process an index page into the package-page index
for match in HREF.finditer(page):
try:
- scan(urllib.parse.urljoin(url, htmldecode(match.group(1))))
+ self._scan(urllib.parse.urljoin(url, htmldecode(match.group(1))))
except ValueError:
pass
- pkg, ver = scan(url) # ensure this page is in the page index
- if pkg:
- # process individual package page
- for new_url in find_external_links(url, page):
- # Process the found URL
- base, frag = egg_info_for_url(new_url)
- if base.endswith('.py') and not frag:
- if ver:
- new_url += '#egg=%s-%s' % (pkg, ver)
- else:
- self.need_version_info(url)
- self.scan_url(new_url)
-
- return PYPI_MD5.sub(
- lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page
- )
- else:
+ pkg, ver = self._scan(url) # ensure this page is in the page index
+ if not pkg:
return "" # no sense double-scanning non-package pages
+ # process individual package page
+ for new_url in find_external_links(url, page):
+ # Process the found URL
+ base, frag = egg_info_for_url(new_url)
+ if base.endswith('.py') and not frag:
+ if ver:
+ new_url += '#egg=%s-%s' % (pkg, ver)
+ else:
+ self.need_version_info(url)
+ self.scan_url(new_url)
+
+ return PYPI_MD5.sub(
+ lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page
+ )
+
def need_version_info(self, url):
self.scan_all(
"Page at %s links to .py file(s) without version info; an index "
@@ -591,7 +596,7 @@ class PackageIndex(Environment):
spec = parse_requirement_arg(spec)
return getattr(self.fetch_distribution(spec, tmpdir), 'location', None)
- def fetch_distribution(
+ def fetch_distribution( # noqa: C901 # is too complex (14) # FIXME
self, requirement, tmpdir, force_scan=False, source=False,
develop_ok=False, local_index=None):
"""Obtain a distribution suitable for fulfilling `requirement`
@@ -762,7 +767,8 @@ class PackageIndex(Environment):
def reporthook(self, url, filename, blocknum, blksize, size):
pass # no-op
- def open_url(self, url, warning=None):
+ # FIXME:
+ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
if url.startswith('file:'):
return local_open(url)
try:
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index eac5e656..b58cca37 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -56,7 +56,7 @@ if not CertificateError:
pass
-if not match_hostname:
+if not match_hostname: # noqa: C901 # 'If 59' is too complex (21) # FIXME
def _dnsname_match(dn, hostname, max_wildcards=1):
"""Matching according to RFC 6125, section 6.4.3
diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py
deleted file mode 100644
index 71194b9d..00000000
--- a/setuptools/tests/files.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-
-
-def build_files(file_defs, prefix=""):
- """
- 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
- dictionary interpreted as the files within it, recursively.
-
- For example:
-
- {"README.txt": "A README file",
- "foo": {
- "__init__.py": "",
- "bar": {
- "__init__.py": "",
- },
- "baz.py": "# Some code",
- }
- }
- """
- for name, contents in file_defs.items():
- full_name = os.path.join(prefix, name)
- if isinstance(contents, dict):
- os.makedirs(full_name, exist_ok=True)
- build_files(contents, prefix=full_name)
- else:
- if isinstance(contents, bytes):
- with open(full_name, 'wb') as f:
- f.write(contents)
- else:
- with open(full_name, 'w') as f:
- f.write(contents)
diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py
index 5204c8d1..a5a172e0 100644
--- a/setuptools/tests/fixtures.py
+++ b/setuptools/tests/fixtures.py
@@ -1,9 +1,14 @@
+import contextlib
+import sys
+import shutil
+import subprocess
+
import pytest
from . import contexts
-@pytest.yield_fixture
+@pytest.fixture
def user_override(monkeypatch):
"""
Override site.USER_BASE and site.USER_SITE with temporary directories in
@@ -17,7 +22,53 @@ def user_override(monkeypatch):
yield
-@pytest.yield_fixture
+@pytest.fixture
def tmpdir_cwd(tmpdir):
with tmpdir.as_cwd() as orig:
yield orig
+
+
+@pytest.fixture
+def tmp_src(request, tmp_path):
+ """Make a copy of the source dir under `$tmp/src`.
+
+ This fixture is useful whenever it's necessary to run `setup.py`
+ or `pip install` against the source directory when there's no
+ control over the number of simultaneous invocations. Such
+ concurrent runs create and delete directories with the same names
+ under the target directory and so they influence each other's runs
+ when they are not being executed sequentially.
+ """
+ tmp_src_path = tmp_path / 'src'
+ shutil.copytree(request.config.rootdir, tmp_src_path)
+ return tmp_src_path
+
+
+@pytest.fixture(autouse=True, scope="session")
+def workaround_xdist_376(request):
+ """
+ Workaround pytest-dev/pytest-xdist#376
+
+ ``pytest-xdist`` tends to inject '' into ``sys.path``,
+ which may break certain isolation expectations.
+ Remove the entry so the import
+ machinery behaves the same irrespective of xdist.
+ """
+ if not request.config.pluginmanager.has_plugin('xdist'):
+ return
+
+ with contextlib.suppress(ValueError):
+ sys.path.remove('')
+
+
+@pytest.fixture
+def sample_project(tmp_path):
+ """
+ Clone the 'sampleproject' and return a path to it.
+ """
+ cmd = ['git', 'clone', 'https://github.com/pypa/sampleproject']
+ try:
+ subprocess.check_call(cmd, cwd=str(tmp_path))
+ except Exception:
+ pytest.skip("Unable to clone sampleproject")
+ return tmp_path / 'sampleproject'
diff --git a/setuptools/tests/requirements.txt b/setuptools/tests/requirements.txt
index d0d07f70..b2d84a94 100644
--- a/setuptools/tests/requirements.txt
+++ b/setuptools/tests/requirements.txt
@@ -11,3 +11,4 @@ paver; python_version>="3.6"
futures; python_version=="2.7"
pip>=19.1 # For proper file:// URLs support.
jaraco.envs
+sphinx
diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py
index 7e213230..6717c053 100644
--- a/setuptools/tests/server.py
+++ b/setuptools/tests/server.py
@@ -65,7 +65,7 @@ class MockServer(http.server.HTTPServer, threading.Thread):
http.server.HTTPServer.__init__(
self, server_address, RequestHandlerClass)
threading.Thread.__init__(self)
- self.setDaemon(True)
+ self.daemon = True
self.requests = []
def run(self):
diff --git a/setuptools/tests/test_bdist_deprecations.py b/setuptools/tests/test_bdist_deprecations.py
deleted file mode 100644
index 704164aa..00000000
--- a/setuptools/tests/test_bdist_deprecations.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""develop tests
-"""
-import mock
-
-import pytest
-
-from setuptools.dist import Distribution
-from setuptools import SetuptoolsDeprecationWarning
-
-
-@mock.patch("distutils.command.bdist_wininst.bdist_wininst")
-def test_bdist_wininst_warning(distutils_cmd):
- dist = Distribution(dict(
- script_name='setup.py',
- script_args=['bdist_wininst'],
- name='foo',
- py_modules=['hi'],
- ))
- dist.parse_command_line()
- with pytest.warns(SetuptoolsDeprecationWarning):
- dist.run_commands()
-
- distutils_cmd.run.assert_called_once()
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index 8760ea30..fb5b90b1 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -7,7 +7,6 @@ import zipfile
import pytest
from setuptools.dist import Distribution
-from setuptools import SetuptoolsDeprecationWarning
from . import contexts
@@ -65,17 +64,3 @@ class Test:
names = list(zi.filename for zi in zip.filelist)
assert 'hi.pyc' in names
assert 'hi.py' not in names
-
- def test_eggsecutable_warning(self, setup_context, user_override):
- dist = Distribution(dict(
- script_name='setup.py',
- script_args=['bdist_egg'],
- name='foo',
- py_modules=['hi'],
- entry_points={
- 'setuptools.installation':
- ['eggsecutable = my_package.some_module:main_func']},
- ))
- dist.parse_command_line()
- with pytest.warns(SetuptoolsDeprecationWarning):
- dist.run_commands()
diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py
index 838fdb42..b6deebe4 100644
--- a/setuptools/tests/test_build_ext.py
+++ b/setuptools/tests/test_build_ext.py
@@ -2,12 +2,13 @@ import sys
import distutils.command.build_ext as orig
from distutils.sysconfig import get_config_var
+from jaraco import path
+
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
@@ -103,10 +104,10 @@ def test_build_ext_config_handling(tmpdir_cwd):
'setup.cfg': DALS(
"""
[build]
- build-base = foo_build
+ build_base = foo_build
"""),
}
- build_files(files)
+ path.build(files)
code, output = environment.run_setup_py(
cmd=['build'], data_stream=(0, 2),
)
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
index 5462b26a..ab75a189 100644
--- a/setuptools/tests/test_build_meta.py
+++ b/setuptools/tests/test_build_meta.py
@@ -3,15 +3,16 @@ import shutil
import tarfile
import importlib
from concurrent import futures
+import re
import pytest
+from jaraco import path
-from .files import build_files
from .textwrap import DALS
class BuildBackendBase:
- def __init__(self, cwd=None, env={}, backend_name='setuptools.build_meta'):
+ def __init__(self, cwd='.', env={}, backend_name='setuptools.build_meta'):
self.cwd = cwd
self.env = env
self.backend_name = backend_name
@@ -108,7 +109,7 @@ defns = [
'setup.cfg': DALS("""
[metadata]
name = foo
- version='0.0.0'
+ version = 0.0.0
[options]
py_modules=hello
@@ -126,11 +127,11 @@ class TestBuildMetaBackend:
backend_name = 'setuptools.build_meta'
def get_build_backend(self):
- return BuildBackend(cwd='.', backend_name=self.backend_name)
+ return BuildBackend(backend_name=self.backend_name)
@pytest.fixture(params=defns)
def build_backend(self, tmpdir, request):
- build_files(request.param, prefix=str(tmpdir))
+ path.build(request.param, prefix=str(tmpdir))
with tmpdir.as_cwd():
yield self.get_build_backend()
@@ -170,7 +171,7 @@ class TestBuildMetaBackend:
"""),
}
- build_files(files)
+ path.build(files)
dist_dir = os.path.abspath('preexisting-' + build_type)
@@ -262,7 +263,7 @@ class TestBuildMetaBackend:
build-backend = "setuptools.build_meta
"""),
}
- build_files(files)
+ path.build(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:
@@ -271,7 +272,7 @@ class TestBuildMetaBackend:
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])
+ path.build(defns[0])
build_backend = self.get_build_backend()
targz_path = build_backend.build_sdist("temp")
@@ -293,7 +294,7 @@ class TestBuildMetaBackend:
""")
}
- build_files(files)
+ path.build(files)
build_backend = self.get_build_backend()
targz_path = build_backend.build_sdist("temp")
@@ -315,7 +316,7 @@ class TestBuildMetaBackend:
""")
}
- build_files(files)
+ path.build(files)
build_backend = self.get_build_backend()
build_backend.build_sdist("temp")
@@ -335,9 +336,9 @@ class TestBuildMetaBackend:
}
def test_build_sdist_relative_path_import(self, tmpdir_cwd):
- build_files(self._relative_path_import_files)
+ path.build(self._relative_path_import_files)
build_backend = self.get_build_backend()
- with pytest.raises(ImportError):
+ with pytest.raises(ImportError, match="^No module named 'hello'$"):
build_backend.build_sdist("temp")
@pytest.mark.parametrize('setup_literal, requirements', [
@@ -374,7 +375,7 @@ class TestBuildMetaBackend:
"""),
}
- build_files(files)
+ path.build(files)
build_backend = self.get_build_backend()
@@ -409,7 +410,7 @@ class TestBuildMetaBackend:
"""),
}
- build_files(files)
+ path.build(files)
build_backend = self.get_build_backend()
@@ -437,11 +438,21 @@ class TestBuildMetaBackend:
}
def test_sys_argv_passthrough(self, tmpdir_cwd):
- build_files(self._sys_argv_0_passthrough)
+ path.build(self._sys_argv_0_passthrough)
build_backend = self.get_build_backend()
with pytest.raises(AssertionError):
build_backend.build_sdist("temp")
+ @pytest.mark.parametrize('build_hook', ('build_sdist', 'build_wheel'))
+ def test_build_with_empty_setuppy(self, build_backend, build_hook):
+ files = {'setup.py': ''}
+ path.build(files)
+
+ with pytest.raises(
+ ValueError,
+ match=re.escape('No distribution was found.')):
+ getattr(build_backend, build_hook)("temp")
+
class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
backend_name = 'setuptools.build_meta:__legacy__'
@@ -449,13 +460,13 @@ class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
# 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)
+ path.build(self._relative_path_import_files)
build_backend = self.get_build_backend()
build_backend.build_sdist("temp")
def test_sys_argv_passthrough(self, tmpdir_cwd):
- build_files(self._sys_argv_0_passthrough)
+ path.build(self._sys_argv_0_passthrough)
build_backend = self.get_build_backend()
build_backend.build_sdist("temp")
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 1dee1271..21f1becd 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,3 +1,6 @@
+import types
+import sys
+
import contextlib
import configparser
@@ -7,6 +10,7 @@ from distutils.errors import DistutilsOptionError, DistutilsFileError
from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
+from distutils.core import Command
from .textwrap import DALS
@@ -206,8 +210,8 @@ class TestMetadata:
fake_env(
tmpdir,
'[metadata]\n'
- 'author-email = test@test.com\n'
- 'home-page = http://test.test.com/test/\n'
+ 'author_email = test@test.com\n'
+ 'home_page = http://test.test.com/test/\n'
'summary = Short summary\n'
'platform = a, b\n'
'classifier =\n'
@@ -503,6 +507,44 @@ class TestMetadata:
with get_dist(tmpdir):
pass
+ def test_warn_dash_deprecation(self, tmpdir):
+ # warn_dash_deprecation() is a method in setuptools.dist
+ # remove this test and the method when no longer needed
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'author-email = test@test.com\n'
+ 'maintainer_email = foo@foo.com\n'
+ )
+ msg = ("Usage of dash-separated 'author-email' will not be supported "
+ "in future versions. "
+ "Please use the underscore name 'author_email' instead")
+ with pytest.warns(UserWarning, match=msg):
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.author_email == 'test@test.com'
+ assert metadata.maintainer_email == 'foo@foo.com'
+
+ def test_make_option_lowercase(self, tmpdir):
+ # remove this test and the method make_option_lowercase() in setuptools.dist
+ # when no longer needed
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'Name = foo\n'
+ 'description = Some description\n'
+ )
+ msg = ("Usage of uppercase key 'Name' in 'metadata' will be deprecated in "
+ "future versions. "
+ "Please use lowercase 'name' instead")
+ with pytest.warns(UserWarning, match=msg):
+ with get_dist(tmpdir) as dist:
+ metadata = dist.metadata
+
+ assert metadata.name == 'foo'
+ assert metadata.description == 'Some description'
+
class TestOptions:
@@ -768,6 +810,20 @@ class TestOptions:
}
assert dist.metadata.provides_extras == set(['pdf', 'rest'])
+ def test_dash_preserved_extras_require(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[options.extras_require]\n'
+ 'foo-a = foo\n'
+ 'foo_b = test\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.extras_require == {
+ 'foo-a': ['foo'],
+ 'foo_b': ['test']
+ }
+
def test_entry_points(self, tmpdir):
_, config = fake_env(
tmpdir,
@@ -802,6 +858,24 @@ class TestOptions:
with get_dist(tmpdir) as dist:
assert dist.entry_points == expected
+ def test_case_sensitive_entry_points(self, tmpdir):
+ _, config = fake_env(
+ tmpdir,
+ '[options.entry_points]\n'
+ 'GROUP1 = point1 = pack.module:func, '
+ '.point2 = pack.module2:func_rest [rest]\n'
+ 'group2 = point3 = pack.module:func2\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.entry_points == {
+ 'GROUP1': [
+ 'point1 = pack.module:func',
+ '.point2 = pack.module2:func_rest [rest]',
+ ],
+ 'group2': ['point3 = pack.module:func2']
+ }
+
def test_data_files(self, tmpdir):
fake_env(
tmpdir,
@@ -853,6 +927,26 @@ class TestOptions:
with get_dist(tmpdir) as dist:
dist.parse_config_files()
+ def test_cmdclass(self, tmpdir):
+ class CustomCmd(Command):
+ pass
+
+ m = types.ModuleType('custom_build', 'test package')
+
+ m.__dict__['CustomCmd'] = CustomCmd
+
+ sys.modules['custom_build'] = m
+
+ fake_env(
+ tmpdir,
+ '[options]\n'
+ 'cmdclass =\n'
+ ' customcmd = custom_build.CustomCmd\n'
+ )
+
+ with get_dist(tmpdir) as dist:
+ assert dist.cmdclass == {'customcmd': CustomCmd}
+
saved_dist_init = _Distribution.__init__
diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py
index 9854420e..df8db4e2 100644
--- a/setuptools/tests/test_develop.py
+++ b/setuptools/tests/test_develop.py
@@ -7,6 +7,8 @@ import sys
import io
import subprocess
import platform
+import pathlib
+import textwrap
from setuptools.command import test
@@ -31,7 +33,7 @@ INIT_PY = """print "foo"
"""
-@pytest.yield_fixture
+@pytest.fixture
def temp_user(monkeypatch):
with contexts.tempdir() as user_base:
with contexts.tempdir() as user_site:
@@ -40,7 +42,7 @@ def temp_user(monkeypatch):
yield
-@pytest.yield_fixture
+@pytest.fixture
def test_env(tmpdir, temp_user):
target = tmpdir
foo = target.mkdir('foo')
@@ -199,3 +201,55 @@ class TestNamespaces:
]
with test.test.paths_on_pythonpath([str(target)]):
subprocess.check_call(pkg_resources_imp)
+
+ @staticmethod
+ def install_workaround(site_packages):
+ site_packages.mkdir(parents=True)
+ sc = site_packages / 'sitecustomize.py'
+ sc.write_text(textwrap.dedent("""
+ import site
+ import pathlib
+ here = pathlib.Path(__file__).parent
+ site.addsitedir(str(here))
+ """).lstrip())
+
+ @pytest.mark.xfail(
+ platform.python_implementation() == 'PyPy',
+ reason="Workaround fails on PyPy (why?)",
+ )
+ def test_editable_prefix(self, tmp_path, sample_project):
+ """
+ Editable install to a prefix should be discoverable.
+ """
+ prefix = tmp_path / 'prefix'
+ prefix.mkdir()
+
+ # figure out where pip will likely install the package
+ site_packages = prefix / next(
+ pathlib.Path(path).relative_to(sys.prefix)
+ for path in sys.path
+ if 'site-packages' in path
+ and path.startswith(sys.prefix)
+ )
+
+ # install the workaround
+ self.install_workaround(site_packages)
+
+ env = dict(os.environ, PYTHONPATH=str(site_packages))
+ cmd = [
+ sys.executable,
+ '-m', 'pip',
+ 'install',
+ '--editable',
+ str(sample_project),
+ '--prefix', str(prefix),
+ '--no-build-isolation',
+ ]
+ subprocess.check_call(cmd, env=env)
+
+ # now run 'sample' with the prefix on the PYTHONPATH
+ bin = 'Scripts' if platform.system() == 'Windows' else 'bin'
+ exe = prefix / bin / 'sample'
+ if sys.version_info < (3, 7) and platform.system() == 'Windows':
+ exe = str(exe)
+ subprocess.check_call([exe], env=env)
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index cb47fb58..dcec1734 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -9,6 +9,9 @@ from setuptools.dist import (
_get_unpatched,
check_package_data,
DistDeprecationWarning,
+ check_specifier,
+ rfc822_escape,
+ rfc822_unescape,
)
from setuptools import sic
from setuptools import Distribution
@@ -84,6 +87,9 @@ def __read_test_cases():
('Metadata version 1.1: Provides', params(
provides=['package'],
)),
+ ('Metadata Version 1.0: Short long description', params(
+ long_description='Short long description',
+ )),
('Metadata version 1.1: Obsoletes', params(
obsoletes=['foo'],
)),
@@ -161,6 +167,7 @@ def test_read_metadata(name, attrs):
('metadata_version', dist_class.get_metadata_version),
('provides', dist_class.get_provides),
('description', dist_class.get_description),
+ ('long_description', dist_class.get_long_description),
('download_url', dist_class.get_download_url),
('keywords', dist_class.get_keywords),
('platforms', dist_class.get_platforms),
@@ -323,3 +330,49 @@ def test_check_package_data(package_data, expected_message):
with pytest.raises(
DistutilsSetupError, match=re.escape(expected_message)):
check_package_data(None, str('package_data'), package_data)
+
+
+def test_check_specifier():
+ # valid specifier value
+ attrs = {'name': 'foo', 'python_requires': '>=3.0, !=3.1'}
+ dist = Distribution(attrs)
+ check_specifier(dist, attrs, attrs['python_requires'])
+
+ # invalid specifier value
+ attrs = {'name': 'foo', 'python_requires': ['>=3.0', '!=3.1']}
+ with pytest.raises(DistutilsSetupError):
+ dist = Distribution(attrs)
+
+
+@pytest.mark.parametrize(
+ 'content, result',
+ (
+ pytest.param(
+ "Just a single line",
+ None,
+ id="single_line",
+ ),
+ pytest.param(
+ "Multiline\nText\nwithout\nextra indents\n",
+ None,
+ id="multiline",
+ ),
+ pytest.param(
+ "Multiline\n With\n\nadditional\n indentation",
+ None,
+ id="multiline_with_indentation",
+ ),
+ pytest.param(
+ " Leading whitespace",
+ "Leading whitespace",
+ id="remove_leading_whitespace",
+ ),
+ pytest.param(
+ " Leading whitespace\nIn\n Multiline comment",
+ "Leading whitespace\nIn\n Multiline comment",
+ id="remove_leading_whitespace_multiline",
+ ),
+ )
+)
+def test_rfc822_unescape(content, result):
+ assert (result or content) == rfc822_unescape(rfc822_escape(content))
diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py
index a53773df..0e89921c 100644
--- a/setuptools/tests/test_distutils_adoption.py
+++ b/setuptools/tests/test_distutils_adoption.py
@@ -21,10 +21,10 @@ class VirtualEnv(jaraco.envs.VirtualEnv):
@pytest.fixture
-def venv(tmpdir):
+def venv(tmp_path, tmp_src):
env = VirtualEnv()
- env.root = path.Path(tmpdir)
- env.req = os.getcwd()
+ env.root = path.Path(tmp_path / 'venv')
+ env.req = str(tmp_src)
return env.create()
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 26a5e9a6..a3b2d6e6 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -15,8 +15,10 @@ import zipfile
import mock
import time
import re
+import subprocess
import pytest
+from jaraco import path
from setuptools import sandbox
from setuptools.sandbox import run_setup
@@ -25,7 +27,6 @@ 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
@@ -34,10 +35,19 @@ from setuptools.tests import fail_on_ascii
import pkg_resources
from . import contexts
-from .files import build_files
from .textwrap import DALS
+@pytest.fixture(autouse=True)
+def pip_disable_index(monkeypatch):
+ """
+ Important: Disable the default index for pip to avoid
+ querying packages in the index and potentially resolving
+ and installing packages there.
+ """
+ monkeypatch.setenv('PIP_NO_INDEX', 'true')
+
+
class FakeDist:
def get_entry_map(self, group):
if group != 'console_scripts':
@@ -305,7 +315,7 @@ class TestPTHFileWriter:
assert not pth.dirty
-@pytest.yield_fixture
+@pytest.fixture
def setup_context(tmpdir):
with (tmpdir / 'setup.py').open('w') as f:
f.write(SETUP_PY)
@@ -361,7 +371,7 @@ class TestUserInstallTest:
f.write('Name: foo\n')
return str(tmpdir)
- @pytest.yield_fixture()
+ @pytest.fixture()
def install_target(self, tmpdir):
target = str(tmpdir)
with mock.patch('sys.path', sys.path + [target]):
@@ -406,7 +416,7 @@ class TestUserInstallTest:
)
-@pytest.yield_fixture
+@pytest.fixture
def distutils_package():
distutils_setup_py = SETUP_PY.replace(
'from setuptools import setup',
@@ -445,22 +455,22 @@ class TestSetupRequires:
"""
monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ monkeypatch.setenv('PIP_NO_INDEX', 'false')
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 = [
+ cmd = [
+ sys.executable,
+ '-m', 'setup',
+ 'easy_install',
'--index-url', mock_index.url,
'--exclude-scripts',
'--install-dir', temp_install_dir,
dist_file,
]
- with sandbox.save_argv(['easy_install']):
- # attempt to install the dist. It should
- # fail because it doesn't exist.
- with pytest.raises(SystemExit):
- easy_install_pkg.main(ei_params)
+ subprocess.Popen(cmd).wait()
# there should have been one requests to the server
assert [r.path for r in mock_index.requests] == ['/does-not-exist/']
@@ -618,6 +628,7 @@ class TestSetupRequires:
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('PIP_NO_INDEX', 'false')
monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url)
with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir:
@@ -730,10 +741,10 @@ class TestSetupRequires:
assert eggs == ['dep 1.0']
@pytest.mark.parametrize(
- 'use_legacy_installer,with_dependency_links_in_setup_py',
- itertools.product((False, True), (False, True)))
+ 'with_dependency_links_in_setup_py',
+ (False, True))
def test_setup_requires_with_find_links_in_setup_cfg(
- self, monkeypatch, use_legacy_installer,
+ self, monkeypatch,
with_dependency_links_in_setup_py):
monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
@@ -755,11 +766,9 @@ class TestSetupRequires:
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, # noqa
+ ''').format(
dependency_links=dependency_links))
with open(test_setup_cfg, 'w') as fp:
fp.write(DALS(
@@ -785,7 +794,7 @@ class TestSetupRequires:
# Create source tree for `dep`.
dep_pkg = os.path.join(temp_dir, 'dep')
os.mkdir(dep_pkg)
- build_files({
+ path.build({
'setup.py':
DALS("""
import setuptools
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index dc472af4..80d35774 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -6,15 +6,15 @@ import re
import stat
import time
+import pytest
+from jaraco import path
+
from setuptools.command.egg_info import (
egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
)
from setuptools.dist import Distribution
-import pytest
-
from . import environment
-from .files import build_files
from .textwrap import DALS
from . import contexts
@@ -37,7 +37,7 @@ class TestEggInfo:
""")
def _create_project(self):
- build_files({
+ path.build({
'setup.py': self.setup_script,
'hello.py': DALS("""
def run():
@@ -45,7 +45,7 @@ class TestEggInfo:
""")
})
- @pytest.yield_fixture
+ @pytest.fixture
def env(self):
with contexts.tempdir(prefix='setuptools-test.') as env_dir:
env = Environment(env_dir)
@@ -56,7 +56,7 @@ class TestEggInfo:
for dirname in subs
)
list(map(os.mkdir, env.paths.values()))
- build_files({
+ path.build({
env.paths['home']: {
'.pydistutils.cfg': DALS("""
[egg_info]
@@ -106,7 +106,7 @@ class TestEggInfo:
the file should remain unchanged.
"""
setup_cfg = os.path.join(env.paths['home'], 'setup.cfg')
- build_files({
+ path.build({
setup_cfg: DALS("""
[egg_info]
tag_build =
@@ -159,8 +159,10 @@ class TestEggInfo:
setup()
""")
- build_files({'setup.py': setup_script,
- 'setup.cfg': setup_config})
+ path.build({
+ 'setup.py': setup_script,
+ 'setup.cfg': setup_config,
+ })
# This command should fail with a ValueError, but because it's
# currently configured to use a subprocess, the actual traceback
@@ -193,7 +195,7 @@ class TestEggInfo:
def test_manifest_template_is_read(self, tmpdir_cwd, env):
self._create_project()
- build_files({
+ path.build({
'MANIFEST.in': DALS("""
recursive-include docs *.rst
"""),
@@ -216,8 +218,10 @@ class TestEggInfo:
'''
) % ('' if use_setup_cfg else requires)
setup_config = requires if use_setup_cfg else ''
- build_files({'setup.py': setup_script,
- 'setup.cfg': setup_config})
+ path.build({
+ 'setup.py': setup_script,
+ 'setup.cfg': setup_config,
+ })
mismatch_marker = "python_version<'{this_ver}'".format(
this_ver=sys.version_info[0],
@@ -533,7 +537,7 @@ class TestEggInfo:
'setup.cfg': DALS("""
"""),
'LICENSE': "Test license"
- }, False), # no license_file attribute
+ }, True), # no license_file attribute, LICENSE auto-included
({
'setup.cfg': DALS("""
[metadata]
@@ -541,12 +545,20 @@ class TestEggInfo:
"""),
'MANIFEST.in': "exclude LICENSE",
'LICENSE': "Test license"
- }, False) # license file is manually excluded
+ }, False), # license file is manually excluded
+ pytest.param({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICEN[CS]E*
+ """),
+ 'LICENSE': "Test license",
+ }, True,
+ id="glob_pattern"),
])
def test_setup_cfg_license_file(
self, tmpdir_cwd, env, files, license_in_sources):
self._create_project()
- build_files(files)
+ path.build(files)
environment.run_setup_py(
cmd=['egg_info'],
@@ -621,7 +633,7 @@ class TestEggInfo:
'setup.cfg': DALS("""
"""),
'LICENSE': "Test license"
- }, [], ['LICENSE']), # no license_files attribute
+ }, ['LICENSE'], []), # no license_files attribute, LICENSE auto-included
({
'setup.cfg': DALS("""
[metadata]
@@ -640,12 +652,41 @@ class TestEggInfo:
'MANIFEST.in': "exclude LICENSE-XYZ",
'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license"
- }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded
+ }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # subset is manually excluded
+ pytest.param({
+ 'setup.cfg': "",
+ 'LICENSE-ABC': "ABC license",
+ 'COPYING-ABC': "ABC copying",
+ 'NOTICE-ABC': "ABC notice",
+ 'AUTHORS-ABC': "ABC authors",
+ 'LICENCE-XYZ': "XYZ license",
+ 'LICENSE': "License",
+ 'INVALID-LICENSE': "Invalid license",
+ }, [
+ 'LICENSE-ABC',
+ 'COPYING-ABC',
+ 'NOTICE-ABC',
+ 'AUTHORS-ABC',
+ 'LICENCE-XYZ',
+ 'LICENSE',
+ ], ['INVALID-LICENSE'],
+ # ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*')
+ id="default_glob_patterns"),
+ pytest.param({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE*
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-XYZ': "XYZ notice",
+ }, ['LICENSE-ABC'], ['NOTICE-XYZ'],
+ id="no_default_glob_patterns"),
])
def test_setup_cfg_license_files(
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
self._create_project()
- build_files(files)
+ path.build(files)
environment.run_setup_py(
cmd=['egg_info'],
@@ -745,12 +786,33 @@ class TestEggInfo:
'LICENSE-PQR': "PQR license",
'LICENSE-XYZ': "XYZ license"
# manually excluded
- }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR'])
+ }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']),
+ pytest.param({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE*
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-XYZ': "XYZ notice",
+ }, ['LICENSE-ABC'], ['NOTICE-XYZ'],
+ id="no_default_glob_patterns"),
+ pytest.param({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE*
+ license_files =
+ NOTICE*
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'NOTICE-ABC': "ABC notice",
+ 'AUTHORS-ABC': "ABC authors",
+ }, ['LICENSE-ABC', 'NOTICE-ABC'], ['AUTHORS-ABC'],
+ id="combined_glob_patterrns"),
])
def test_setup_cfg_license_file_license_files(
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
self._create_project()
- build_files(files)
+ path.build(files)
environment.run_setup_py(
cmd=['egg_info'],
@@ -886,7 +948,7 @@ class TestEggInfo:
def test_egg_info_tag_only_once(self, tmpdir_cwd, env):
self._create_project()
- build_files({
+ path.build({
'setup.cfg': DALS("""
[egg_info]
tag_build = dev
diff --git a/setuptools/tests/test_glob.py b/setuptools/tests/test_glob.py
index a0728c5d..e99587f5 100644
--- a/setuptools/tests/test_glob.py
+++ b/setuptools/tests/test_glob.py
@@ -1,9 +1,8 @@
import pytest
+from jaraco import path
from setuptools.glob import glob
-from .files import build_files
-
@pytest.mark.parametrize('tree, pattern, matches', (
('', b'', []),
@@ -31,5 +30,5 @@ from .files import build_files
))
def test_glob(monkeypatch, tmpdir, tree, pattern, matches):
monkeypatch.chdir(tmpdir)
- build_files({name: '' for name in tree.split()})
+ path.build({name: '' for name in tree.split()})
assert list(sorted(glob(pattern))) == list(sorted(matches))
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 24cef480..b5578312 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -6,11 +6,6 @@ Try to install a few packages.
import glob
import os
import sys
-import re
-import subprocess
-import functools
-import tarfile
-import zipfile
import urllib.request
import pytest
@@ -20,6 +15,13 @@ from setuptools.command import easy_install as easy_install_pkg
from setuptools.dist import Distribution
+pytestmark = pytest.mark.skipif(
+ 'platform.python_implementation() == "PyPy" and '
+ 'platform.system() == "Windows"',
+ reason="pypa/setuptools#2496",
+)
+
+
def setup_module(module):
packages = 'stevedore', 'virtualenvwrapper', 'pbr', 'novaclient'
for pkg in packages:
@@ -117,56 +119,3 @@ def test_pyuri(install_context):
# The package data should be installed.
assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex'))
-
-
-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')
-def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
- """
- All setuptools build dependencies must build without
- setuptools.
- """
- if 'pyparsing' in build_dep:
- pytest.xfail(reason="Project imports setuptools unconditionally")
- build_target = tmpdir_factory.mktemp('source')
- build_dir = download_and_extract(request, build_dep, build_target)
- install_target = tmpdir_factory.mktemp('target')
- output = install(build_dir, install_target)
- for line in output.splitlines():
- match = re.search('Unknown distribution option: (.*)', line)
- 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
-
-
-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', 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,
- ]
- output = subprocess.check_output(cmd, encoding='utf-8')
- filename = re.search('Saved (.*)', output).group(1)
- request.addfinalizer(functools.partial(os.remove, filename))
- opener = zipfile.ZipFile if filename.endswith('.zip') else tarfile.open
- with opener(filename) as archive:
- archive.extractall(target)
- return os.path.join(target, os.listdir(target)[0])
diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py
index 82bdb9c6..589cefb2 100644
--- a/setuptools/tests/test_manifest.py
+++ b/setuptools/tests/test_manifest.py
@@ -55,6 +55,7 @@ def touch(filename):
default_files = frozenset(map(make_local_path, [
'README.rst',
'MANIFEST.in',
+ 'LICENSE',
'setup.py',
'app.egg-info/PKG-INFO',
'app.egg-info/SOURCES.txt',
diff --git a/setuptools/tests/test_msvc.py b/setuptools/tests/test_msvc.py
index 24e38ea8..d1527bfa 100644
--- a/setuptools/tests/test_msvc.py
+++ b/setuptools/tests/test_msvc.py
@@ -88,7 +88,7 @@ class TestModulePatch:
assert isinstance(exc, expected)
assert 'aka.ms/vcpython27' in str(exc)
- @pytest.yield_fixture
+ @pytest.fixture
def user_preferred_setting(self):
"""
Set up environment with different install dirs for user vs. system
@@ -116,7 +116,7 @@ class TestModulePatch:
expected = os.path.join(user_preferred_setting, 'vcvarsall.bat')
assert expected == result
- @pytest.yield_fixture
+ @pytest.fixture
def local_machine_setting(self):
"""
Set up environment with only the system environment configured.
@@ -138,7 +138,7 @@ class TestModulePatch:
expected = os.path.join(local_machine_setting, 'vcvarsall.bat')
assert expected == result
- @pytest.yield_fixture
+ @pytest.fixture
def x64_preferred_setting(self):
"""
Set up environment with 64-bit and 32-bit system settings configured
diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py
index 6c8c522d..270f90c9 100644
--- a/setuptools/tests/test_namespaces.py
+++ b/setuptools/tests/test_namespaces.py
@@ -62,8 +62,9 @@ class TestNamespaces:
target.mkdir()
install_cmd = [
sys.executable,
- '-m', 'easy_install',
- '-d', str(target),
+ '-m', 'pip',
+ 'install',
+ '-t', str(target),
str(pkg),
]
with test.test.paths_on_pythonpath([str(target)]):
diff --git a/setuptools/tests/test_sphinx_upload_docs.py b/setuptools/tests/test_sphinx_upload_docs.py
new file mode 100644
index 00000000..cc5b8293
--- /dev/null
+++ b/setuptools/tests/test_sphinx_upload_docs.py
@@ -0,0 +1,38 @@
+import pytest
+
+from jaraco import path
+
+from setuptools.command.upload_docs import upload_docs
+from setuptools.dist import Distribution
+
+
+@pytest.fixture
+def sphinx_doc_sample_project(tmpdir_cwd):
+ path.build({
+ 'setup.py': 'from setuptools import setup; setup()',
+ 'build': {
+ 'docs': {
+ 'conf.py': 'project="test"',
+ 'index.rst': ".. toctree::\
+ :maxdepth: 2\
+ :caption: Contents:",
+ },
+ },
+ })
+
+
+@pytest.mark.usefixtures('sphinx_doc_sample_project')
+class TestSphinxUploadDocs:
+ def test_sphinx_doc(self):
+ params = dict(
+ name='foo',
+ packages=['test'],
+ )
+ dist = Distribution(params)
+
+ cmd = upload_docs(dist)
+
+ cmd.initialize_options()
+ assert cmd.upload_dir is None
+ assert cmd.has_sphinx() is True
+ cmd.finalize_options()
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
index a26e32a6..55978aad 100644
--- a/setuptools/tests/test_upload_docs.py
+++ b/setuptools/tests/test_upload_docs.py
@@ -3,6 +3,7 @@ import zipfile
import contextlib
import pytest
+from jaraco import path
from setuptools.command.upload_docs import upload_docs
from setuptools.dist import Distribution
@@ -10,28 +11,20 @@ from setuptools.dist import Distribution
from .textwrap import DALS
from . import contexts
-SETUP_PY = DALS(
- """
- from setuptools import setup
-
- setup(name='foo')
- """)
-
@pytest.fixture
def sample_project(tmpdir_cwd):
- # setup.py
- with open('setup.py', 'wt') as f:
- f.write(SETUP_PY)
-
- os.mkdir('build')
-
- # A test document.
- with open('build/index.html', 'w') as f:
- f.write("Hello world.")
-
- # An empty folder.
- os.mkdir('build/empty')
+ path.build({
+ 'setup.py': DALS("""
+ from setuptools import setup
+
+ setup(name='foo')
+ """),
+ 'build': {
+ 'index.html': 'Hello world.',
+ 'empty': {},
+ }
+ })
@pytest.mark.usefixtures('sample_project')
diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py
index c8ed9e57..399dbaf0 100644
--- a/setuptools/tests/test_virtualenv.py
+++ b/setuptools/tests/test_virtualenv.py
@@ -1,9 +1,11 @@
import glob
import os
import sys
+import itertools
+
+import pathlib
import pytest
-from pytest import yield_fixture
from pytest_fixture_config import yield_requires_config
import pytest_virtualenv
@@ -27,7 +29,7 @@ def pytest_virtualenv_works(virtualenv):
@yield_requires_config(pytest_virtualenv.CONFIG, ['virtualenv_executable'])
-@yield_fixture(scope='function')
+@pytest.fixture(scope='function')
def bare_virtualenv():
""" Bare virtualenv (no pip/setuptools/wheel).
"""
@@ -39,14 +41,11 @@ def bare_virtualenv():
yield venv
-SOURCE_DIR = os.path.join(os.path.dirname(__file__), '../..')
-
-
-def test_clean_env_install(bare_virtualenv):
+def test_clean_env_install(bare_virtualenv, tmp_src):
"""
Check setuptools can be installed in a clean environment.
"""
- bare_virtualenv.run(['python', 'setup.py', 'install'], cd=SOURCE_DIR)
+ bare_virtualenv.run(['python', 'setup.py', 'install'], cd=tmp_src)
def _get_pip_versions():
@@ -67,24 +66,38 @@ def _get_pip_versions():
# No network, disable most of these tests
network = False
+ def mark(param, *marks):
+ if not isinstance(param, type(pytest.param(''))):
+ param = pytest.param(param)
+ return param._replace(marks=param.marks + marks)
+
+ def skip_network(param):
+ return param if network else mark(param, pytest.mark.skip(reason="no network"))
+
+ issue2599 = pytest.mark.skipif(
+ sys.version_info > (3, 10),
+ reason="pypa/setuptools#2599",
+ )
+
network_versions = [
- 'pip==9.0.3',
- 'pip==10.0.1',
- 'pip==18.1',
- 'pip==19.0.1',
- 'https://github.com/pypa/pip/archive/master.zip',
+ mark('pip==9.0.3', issue2599),
+ mark('pip==10.0.1', issue2599),
+ mark('pip==18.1', issue2599),
+ mark('pip==19.3.1', pytest.mark.xfail(reason='pypa/pip#6599')),
+ 'pip==20.0.2',
+ 'https://github.com/pypa/pip/archive/main.zip',
]
- versions = [None] + [
- pytest.param(v, **({} if network else {'marks': pytest.mark.skip}))
- for v in network_versions
- ]
+ versions = itertools.chain(
+ [None],
+ map(skip_network, network_versions)
+ )
- return versions
+ return list(versions)
@pytest.mark.parametrize('pip_version', _get_pip_versions())
-def test_pip_upgrade_from_source(pip_version, virtualenv):
+def test_pip_upgrade_from_source(pip_version, tmp_src, virtualenv):
"""
Check pip can upgrade setuptools from source.
"""
@@ -103,7 +116,7 @@ def test_pip_upgrade_from_source(pip_version, virtualenv):
virtualenv.run(' && '.join((
'python setup.py -q sdist -d {dist}',
'python setup.py -q bdist_wheel -d {dist}',
- )).format(dist=dist_dir), cd=SOURCE_DIR)
+ )).format(dist=dist_dir), cd=tmp_src)
sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0]
wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0]
# Then update from wheel.
@@ -112,19 +125,19 @@ def test_pip_upgrade_from_source(pip_version, virtualenv):
virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
-def _check_test_command_install_requirements(virtualenv, tmpdir):
+def _check_test_command_install_requirements(virtualenv, tmpdir, cwd):
"""
Check the test command will install all required dependencies.
"""
# Install setuptools.
- virtualenv.run('python setup.py develop', cd=SOURCE_DIR)
+ virtualenv.run('python setup.py develop', cd=cwd)
def sdist(distname, version):
dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version))
make_nspkg_sdist(str(dist_path), distname, version)
return dist_path
dependency_links = [
- str(dist_path)
+ pathlib.Path(str(dist_path)).as_uri()
for dist_path in (
sdist('foobar', '2.4'),
sdist('bits', '4.2'),
@@ -174,22 +187,21 @@ def _check_test_command_install_requirements(virtualenv, tmpdir):
assert tmpdir.join('success').check()
-def test_test_command_install_requirements(virtualenv, tmpdir):
+def test_test_command_install_requirements(virtualenv, tmpdir, request):
# 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)
+ # uninstall setuptools so that 'setup.py develop' works
+ virtualenv.run("python -m pip uninstall -y setuptools")
+ # disable index URL so bits and bobs aren't requested from PyPI
+ virtualenv.env['PIP_NO_INDEX'] = '1'
+ _check_test_command_install_requirements(virtualenv, tmpdir, request.config.rootdir)
-def test_no_missing_dependencies(bare_virtualenv):
+def test_no_missing_dependencies(bare_virtualenv, request):
"""
Quick and dirty test to ensure all external dependencies are vendored.
"""
for command in ('upload',): # sorted(distutils.command.__all__):
- bare_virtualenv.run(
- ['python', 'setup.py', command, '-h'], cd=SOURCE_DIR)
+ cmd = ['python', 'setup.py', command, '-h']
+ bare_virtualenv.run(cmd, cd=request.config.rootdir)
diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py
index e56eac14..7345b135 100644
--- a/setuptools/tests/test_wheel.py
+++ b/setuptools/tests/test_wheel.py
@@ -15,6 +15,7 @@ import sys
import zipfile
import pytest
+from jaraco import path
from pkg_resources import Distribution, PathMetadata, PY_MAJOR
from setuptools.extern.packaging.utils import canonicalize_name
@@ -22,7 +23,6 @@ from setuptools.extern.packaging.tags import parse_tag
from setuptools.wheel import Wheel
from .contexts import tempdir
-from .files import build_files
from .textwrap import DALS
@@ -91,7 +91,7 @@ def build_wheel(extra_file_defs=None, **kwargs):
if extra_file_defs:
file_defs.update(extra_file_defs)
with tempdir() as source_dir:
- build_files(file_defs, source_dir)
+ path.build(file_defs, source_dir)
subprocess.check_call((sys.executable, 'setup.py',
'-q', 'bdist_wheel'), cwd=source_dir)
yield glob.glob(os.path.join(source_dir, 'dist', '*.whl'))[0]
diff --git a/skeleton.md b/skeleton.md
new file mode 100644
index 00000000..0938f892
--- /dev/null
+++ b/skeleton.md
@@ -0,0 +1,166 @@
+# Overview
+
+This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution.
+
+## An SCM-Managed Approach
+
+While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a Git repo capturing the evolution and culmination of these best practices.
+
+It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter.
+
+The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects.
+
+Another advantage to using an SCM-managed approach is that tools like GitHub recognize that a change in the skeleton is the _same change_ across all projects that merge with that skeleton. Without the ancestry, with a traditional copy/paste approach, a [commit like this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5) would produce notifications in the upstream project issue for each and every application, but because it's centralized, GitHub provides just the one notification when the change is added to the skeleton.
+
+# Usage
+
+## new projects
+
+To use skeleton for a new project, simply pull the skeleton into a new project:
+
+```
+$ git init my-new-project
+$ cd my-new-project
+$ git pull gh://jaraco/skeleton
+```
+
+Now customize the project to suit your individual project needs.
+
+## existing projects
+
+If you have an existing project, you can still incorporate the skeleton by merging it into the codebase.
+
+```
+$ git merge skeleton --allow-unrelated-histories
+```
+
+The `--allow-unrelated-histories` is necessary because the history from the skeleton was previously unrelated to the existing codebase. Resolve any merge conflicts and commit to the master, and now the project is based on the shared skeleton.
+
+## Updating
+
+Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar Git operations.
+
+For example, here's a session of the [path project](https://pypi.org/project/path) pulling non-conflicting changes from the skeleton:
+
+<img src="https://raw.githubusercontent.com/jaraco/skeleton/gh-pages/docs/refresh.svg">
+
+Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints.
+
+## Periodic Collapse
+
+In late 2020, this project [introduced](https://github.com/jaraco/skeleton/issues/27) the idea of a periodic but infrequent (O(years)) collapse of commits to limit the number of commits a new consumer will need to accept to adopt the skeleton.
+
+The full history of commits is collapsed into a single commit and that commit becomes the new mainline head.
+
+When one of these collapse operations happens, any project that previously pulled from the skeleton will no longer have a related history with that new main branch. For those projects, the skeleton provides a "handoff" branch that reconciles the two branches. Any project that has previously merged with the skeleton but now gets an error "fatal: refusing to merge unrelated histories" should instead use the handoff branch once to incorporate the new main branch.
+
+```
+$ git pull https://github.com/jaraco/skeleton 2020-handoff
+```
+
+This handoff needs to be pulled just once and thereafter the project can pull from the main head.
+
+The archive and handoff branches from prior collapses are indicate here:
+
+| refresh | archive | handoff |
+|---------|-----------------|--------------|
+| 2020-12 | archive/2020-12 | 2020-handoff |
+
+# Features
+
+The features/techniques employed by the skeleton include:
+
+- PEP 517/518-based build relying on Setuptools as the build tool
+- Setuptools declarative configuration using setup.cfg
+- tox for running tests
+- A README.rst as reStructuredText with some popular badges, but with Read the Docs and AppVeyor badges commented out
+- A CHANGES.rst file intended for publishing release notes about the project
+- Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier)
+- Integrated type checking through [mypy](https://github.com/python/mypy/).
+
+## Packaging Conventions
+
+A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on Setuptools (a minimum version compatible with setup.cfg declarative config).
+
+The setup.cfg file implements the following features:
+
+- Assumes universal wheel for release
+- Advertises the project's LICENSE file (MIT by default)
+- Reads the README.rst file into the long description
+- Some common Trove classifiers
+- Includes all packages discovered in the repo
+- Data files in the package are also included (not just Python files)
+- Declares the required Python versions
+- Declares install requirements (empty by default)
+- Declares setup requirements for legacy environments
+- Supplies two 'extras':
+ - testing: requirements for running tests
+ - docs: requirements for building docs
+ - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project); these markers help avoid merge conflicts
+- Placeholder for defining entry points
+
+Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things:
+
+- derive the project version from SCM tags
+- ensure that all files committed to the repo are automatically included in releases
+
+## Running Tests
+
+The skeleton assumes the developer has [tox](https://pypi.org/project/tox) installed. The developer is expected to run `tox` to run tests on the current Python version using [pytest](https://pypi.org/project/pytest).
+
+Other environments (invoked with `tox -e {name}`) supplied include:
+
+ - a `docs` environment to build the documentation
+ - a `release` environment to publish the package to PyPI
+
+A pytest.ini is included to define common options around running tests. In particular:
+
+- rely on default test discovery in the current directory
+- avoid recursing into common directories not containing tests
+- run doctests on modules and invoke Flake8 tests
+- in doctests, allow Unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option.
+- filters out known warnings caused by libraries/functionality included by the skeleton
+
+Relies on a .flake8 file to correct some default behaviors:
+
+- disable mutually incompatible rules W503 and W504
+- support for Black format
+
+## Continuous Integration
+
+The project is pre-configured to run Continuous Integration tests.
+
+### Github Actions
+
+[Github Actions](https://docs.github.com/en/free-pro-team@latest/actions) are the preferred provider as they provide free, fast, multi-platform services with straightforward configuration. Configured in `.github/workflows`.
+
+Features include:
+- test against multiple Python versions
+- run on late (and updated) platform versions
+- automated releases of tagged commits
+- [automatic merging of PRs](https://github.com/marketplace/actions/merge-pull-requests) (requires [protecting branches with required status checks](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/enabling-required-status-checks), [not possible through API](https://github.community/t/set-all-status-checks-to-be-required-as-branch-protection-using-the-github-api/119493))
+
+
+### Continuous Deployments
+
+In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example:
+
+```
+pip-run -q jaraco.develop -- -m jaraco.develop.add-github-secrets
+```
+
+## Building Documentation
+
+Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`.
+
+In addition to building the Sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs.
+
+## Cutting releases
+
+By default, tagged commits are released through the continuous integration deploy stage.
+
+Releases may also be cut manually by invoking the tox environment `release` with the PyPI token set as the TWINE_PASSWORD:
+
+```
+TWINE_PASSWORD={token} tox -e release
+```
diff --git a/tools/tox_pip.py b/tools/tox_pip.py
deleted file mode 100644
index be2ff1d0..00000000
--- a/tools/tox_pip.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import os
-import subprocess
-import sys
-import re
-
-
-def remove_setuptools():
- """
- Remove setuptools from the current environment.
- """
- print("Removing setuptools")
- cmd = [sys.executable, '-m', 'pip', 'uninstall', '-y', 'setuptools']
- # set cwd to something other than '.' to avoid detecting
- # '.' as the installed package.
- subprocess.check_call(cmd, cwd=os.environ['TOX_WORK_DIR'])
-
-
-def bootstrap():
- print("Running bootstrap")
- cmd = [sys.executable, '-m', 'bootstrap']
- subprocess.check_call(cmd)
-
-
-def is_install_self(args):
- """
- Do the args represent an install of .?
- """
- def strip_extras(arg):
- match = re.match(r'(.*)?\[.*\]$', arg)
- return match.group(1) if match else arg
-
- return (
- 'install' in args
- and any(
- arg in ['.', os.getcwd()]
- for arg in map(strip_extras, args)
- )
- )
-
-
-def pip(*args):
- cmd = [sys.executable, '-m', 'pip'] + list(args)
- return subprocess.check_call(cmd)
-
-
-def test_dependencies():
- from ConfigParser import ConfigParser
-
- def clean(dep):
- spec, _, _ = dep.partition('#')
- return spec.strip()
-
- parser = ConfigParser()
- parser.read('setup.cfg')
- raw = parser.get('options.extras_require', 'tests').split('\n')
- return filter(None, map(clean, raw))
-
-
-def run(args):
- os.environ['PIP_USE_PEP517'] = 'true'
-
- if is_install_self(args):
- remove_setuptools()
- bootstrap()
-
- pip(*args)
-
-
-if __name__ == '__main__':
- run(sys.argv[1:])
diff --git a/towncrier_template.rst b/towncrier_template.rst
index 31098b7c..7f507342 100644
--- a/towncrier_template.rst
+++ b/towncrier_template.rst
@@ -1,3 +1,7 @@
+{% if top_line %}
+{{ top_line }}
+{{ top_underline * ((top_line)|length)}}
+{% endif %}
{% for section, _ in sections.items() %}
{% set underline = underlines[0] %}{% if section %}{{section}}
{{ underline * section|length }}
diff --git a/tox.ini b/tox.ini
index 828d2c02..04e77ed5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,31 +1,20 @@
-# To run Tox against all supported Python interpreters, you can set:
-#
-# export TOXENV='py3{5,6,7,8},pypy,pypy3'
-
[tox]
-envlist=python
+envlist = python
minversion = 3.2
-requires =
- tox-pip-version >= 0.0.6
-
-[helpers]
-# Custom pip behavior
-pip = python {toxinidir}/tools/tox_pip.py
+# https://github.com/jaraco/skeleton/issues/6
+tox_pip_extensions_ext_venv_update = true
+toxworkdir={env:TOX_WORK_DIR:.tox}
[testenv]
-pip_version = pip
-install_command = {[helpers]pip} install {opts} {packages}
-list_dependencies_command = {[helpers]pip} freeze --all
+deps =
+commands =
+ pytest {posargs}
+usedevelop = True
+extras = testing
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 Program* CommonProgram* VS* APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED
-commands = pytest {posargs}
-usedevelop=True
-extras =
- tests
-
+passenv =
+ windir # required for test_pkg_resources
[testenv:coverage]
description=Combine coverage data and create report
@@ -45,20 +34,11 @@ commands=codecov -X gcov --file {toxworkdir}/coverage.xml
[testenv:docs]
extras =
- docs
- testing
+ docs
+ testing
changedir = docs
commands =
- {envpython} -m sphinx \
- -j auto \
- -b html \
- --color \
- -a \
- -n \
- -W \
- -d "{temp_dir}/.doctrees" \
- . \
- "{toxinidir}/build/html"
+ python -m sphinx -W . {toxinidir}/build/html
[testenv:finalize]
skip_install = True
@@ -72,21 +52,20 @@ commands =
[testenv:release]
skip_install = True
deps =
- wheel
- twine[keyring]>=1.13
+ build
+ twine>=3
path
jaraco.develop>=7.1
- jaraco.tidelift
passenv =
TWINE_PASSWORD
GITHUB_TOKEN
- 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
+ # unset tag_build and tag_date pypa/setuptools#2500
+ python setup.py egg_info -Db "" saveopts
+ python -m build
python -m twine upload dist/*
python -m jaraco.develop.create-github-release
- python -m jaraco.tidelift.publish-release-notes