Commit 619e229c authored by Jason R. Coombs's avatar Jason R. Coombs

Merge branch 'master' into docs/setup.cfg-only

parents b0657c80 e04c75ab
[bumpversion] [bumpversion]
current_version = 45.0.0 current_version = 46.3.1
commit = True commit = True
tag = True tag = True
[bumpversion:file:setup.cfg] [bumpversion:file:setup.cfg]
[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
tidelift: pypi/setuptools
---
name: Setuptools warns about Python 2 incompatibility
about: Report the issue where setuptools 45 or later stops working on Python 2
title: Incompatible install in (summarize your environment)
labels: Python 2
assignees: ''
---
<!--
Please DO NOT SUBMIT this template without first investigating the issue and answering the questions below. This template is intended mainly for developers of systems and not for end users. If you are an end user experiencing the warning, please work with your system maintainers (starting with the project you're trying to use) to report the issue.
If you did not intend to use this template, but only meant to file a blank issue, just hit the back button and click "Open a blank issue".
It's by design that Setuptools 45 and later will stop working on Python 2. To ease the transition, Setuptools 45 was released to continue to have Python 2 compatibility, but emit a strenuous warning that it will stop working.
In most cases, using pip 9 or later to install Setuptools from PyPI or any index supporting the Requires-Python metadata will do the right thing and install Setuptools 44.x on Python 2.
If you've come to file an issue, it's probably because some process managed to bypass these protections.
Your first course of action should be to reason about how you managed to get an unsupported version of Setuptools on Python 2. Please complete the sections below and provide any other detail about your environment that will help us help you.
-->
## Prerequisites
<!-- These are the recommended workarounds for the issue. Please
try them first. -->
- [ ] Python 2 is required for this application.
- [ ] I maintain the software that installs Setuptools (if not, please contact that project).
- [ ] Setuptools installed with pip 9 or later.
- [ ] Pinning Setuptools to `setuptools<45` in the environment was unsuccessful.
## Environment Details
- Operating System and version:
- Python version:
- Python installed how:
- Virtualenv version (if using virtualenv): n/a
Command(s) used to install setuptools (and output):
```
```
Output of `pip --version` when installing setuptools:
```
```
## Other notes
name: Test suite name: >-
👷
Test suite
on: on:
push: push:
...@@ -8,32 +10,55 @@ on: ...@@ -8,32 +10,55 @@ on:
jobs: jobs:
tests: tests:
name: 👷 name: >-
${{ matrix.python-version }}
/
${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
# max-parallel: 5 # max-parallel: 5
matrix: matrix:
python-version: python-version:
- 3.8 - 3.8
- pypy3
- 3.7 - 3.7
- 3.6 - 3.6
- 3.5 - 3.5
- 2.7
os: os:
- ubuntu-18.04 - ubuntu-latest
- ubuntu-16.04 - ubuntu-16.04
- macOS-latest - macOS-latest
# - windows-2019 # - windows-2019
# - windows-2016 # - windows-2016
env:
- TOXENV: python env:
NETWORK_REQUIRED: 1
TOX_PARALLEL_NO_SPINNER: 1
TOXENV: python
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1 uses: actions/setup-python@v1.1.1
with:
python-version: ${{ matrix.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: with:
version: ${{ matrix.python-version }} path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Upgrade pip/setuptools/wheel - name: Upgrade pip/setuptools/wheel
run: >- run: >-
python python
...@@ -47,15 +72,14 @@ jobs: ...@@ -47,15 +72,14 @@ jobs:
- name: Log installed dists - name: Log installed dists
run: >- run: >-
python -m pip freeze --all python -m pip freeze --all
- name: Adjust TOXENV for PyPy
if: startsWith(matrix.python-version, 'pypy')
run: >-
echo "::set-env name=TOXENV::${{ matrix.python-version }}"
- name: Log env vars - name: Log env vars
run: >- run: >-
env env
env: ${{ matrix.env }}
- name: Update egg_info based on setup.py in checkout
run: >-
python -m bootstrap
env: ${{ matrix.env }}
- name: Verify that there's no cached Python modules in sources - name: Verify that there's no cached Python modules in sources
if: >- if: >-
! startsWith(matrix.os, 'windows-') ! startsWith(matrix.os, 'windows-')
...@@ -63,14 +87,16 @@ jobs: ...@@ -63,14 +87,16 @@ jobs:
! grep pyc setuptools.egg-info/SOURCES.txt ! grep pyc setuptools.egg-info/SOURCES.txt
- name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}'
run: | run: >-
python -m tox --parallel auto --notest --skip-missing-interpreters false python -m
env: ${{ matrix.env }} tox
--parallel auto
--notest
--skip-missing-interpreters false
- name: Test with tox - name: Test with tox
run: | run: >-
${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 python -m
python -m tox \ tox
--parallel 0 \ --parallel auto
-- \ --
--cov --cov
env: ${{ matrix.env }}
...@@ -19,3 +19,4 @@ setuptools.egg-info ...@@ -19,3 +19,4 @@ setuptools.egg-info
.cache .cache
.idea/ .idea/
.pytest_cache/ .pytest_cache/
.mypy_cache/
python: python:
version: 3 version: 3
requirements_file: docs/requirements.txt extra_requirements:
- docs
pip_install: false pip_install: false
requirements: docs/requirements.txt
...@@ -21,11 +21,9 @@ jobs: ...@@ -21,11 +21,9 @@ jobs:
- python: 3.8-dev - python: 3.8-dev
- <<: *latest_py3 - <<: *latest_py3
env: TOXENV=docs DISABLE_COVERAGE=1 env: TOXENV=docs DISABLE_COVERAGE=1
- <<: *latest_py3 allow_failures:
stage: deploy # suppress failures due to pypa/setuptools#2000
if: tag IS present - python: pypy3
script: tox -e release
after_success: skip
cache: pip cache: pip
...@@ -40,8 +38,6 @@ install: ...@@ -40,8 +38,6 @@ install:
- pip freeze --all - pip freeze --all
- env - env
# update egg_info based on setup.py in checkout
- python bootstrap.py
- "! grep pyc setuptools.egg-info/SOURCES.txt" - "! grep pyc setuptools.egg-info/SOURCES.txt"
script: script:
......
v46.3.1
-------
No significant changes.
v46.3.0
-------
* #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.
* #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``.
v46.2.0
-------
* #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.
* #1698: Added documentation for ``build_meta`` (a bare minimum, not completed).
* #2082: Filter ``lib2to3`` ``PendingDeprecationWarning`` and ``DeprecationWarning`` in tests,
because ``lib2to3`` is `deprecated in Python 3.9 <https://bugs.python.org/issue40360>`_.
v46.1.3
-------
No significant changes.
v46.1.2
-------
* #1458: Added template for reporting Python 2 incompatibilities.
v46.1.1
-------
No significant changes.
v46.1.0
-------
* #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
* #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.
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
* #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.
v46.0.0
-------
* #65: Once again as in 3.0, removed the Features feature.
* #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.
* #2011: Fix broken link to distutils docs on package_data
* #1991: Include pkg_resources test data in sdist, so tests can be executed from it.
v45.3.0
-------
* #1557: Deprecated eggsecutable scripts and updated docs.
* #1904: Update msvc.py to use CPython 3.8.0 mechanism to find msvc 14+
v45.2.0
-------
* #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:
* The ``--user`` option is now always available. A warning is issued if the user site directory is not available.
* 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).
* #1968: Add flake8-2020 to check for misuse of sys.version or sys.version_info.
v45.1.0
-------
* #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.
v45.0.0 v45.0.0
------- -------
......
...@@ -4,6 +4,7 @@ recursive-include setuptools/tests *.html ...@@ -4,6 +4,7 @@ recursive-include setuptools/tests *.html
recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
recursive-include setuptools/_vendor *.py *.txt recursive-include setuptools/_vendor *.py *.txt
recursive-include pkg_resources *.py *.txt recursive-include pkg_resources *.py *.txt
recursive-include pkg_resources/tests/data *
include *.py include *.py
include *.rst include *.rst
include MANIFEST.in include MANIFEST.in
......
.. image:: https://img.shields.io/pypi/v/setuptools.svg .. image:: https://img.shields.io/pypi/v/setuptools.svg
:target: https://pypi.org/project/setuptools :target: `PyPI link`_
.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg .. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
:target: https://setuptools.readthedocs.io :target: `PyPI link`_
.. _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://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white .. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white
:target: https://travis-ci.org/pypa/setuptools :target: https://travis-ci.org/pypa/setuptools
...@@ -10,14 +15,15 @@ ...@@ -10,14 +15,15 @@
.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20CI&logo=appveyor&logoColor=white .. 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 :target: https://ci.appveyor.com/project/pypa/setuptools/branch/master
.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg
:target: https://setuptools.readthedocs.io
.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white .. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white
:target: https://codecov.io/gh/pypa/setuptools :target: https://codecov.io/gh/pypa/setuptools
.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat .. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat
:target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme :target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme
.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
See the `Installation Instructions See the `Installation Instructions
<https://packaging.python.org/installing/>`_ in the Python Packaging <https://packaging.python.org/installing/>`_ in the Python Packaging
User's Guide for instructions on installing, upgrading, and uninstalling User's Guide for instructions on installing, upgrading, and uninstalling
......
...@@ -7,6 +7,15 @@ environment: ...@@ -7,6 +7,15 @@ environment:
CODECOV_ENV: APPVEYOR_JOB_NAME CODECOV_ENV: APPVEYOR_JOB_NAME
matrix: matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
APPVEYOR_JOB_NAME: "python35-x64-vs2015"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
APPVEYOR_JOB_NAME: "python35-x64-vs2017"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
APPVEYOR_JOB_NAME: "python35-x64-vs2019"
PYTHON: "C:\\Python35-x64"
- APPVEYOR_JOB_NAME: "python36-x64" - APPVEYOR_JOB_NAME: "python36-x64"
PYTHON: "C:\\Python36-x64" PYTHON: "C:\\Python36-x64"
- APPVEYOR_JOB_NAME: "python37-x64" - APPVEYOR_JOB_NAME: "python37-x64"
...@@ -28,7 +37,6 @@ test_script: ...@@ -28,7 +37,6 @@ test_script:
- python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
- pip install --upgrade tox tox-venv virtualenv - pip install --upgrade tox tox-venv virtualenv
- pip freeze --all - pip freeze --all
- python bootstrap.py
- tox -- --cov - tox -- --cov
after_test: after_test:
......
# 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)
displayName: 'publish to PyPI'
condition: contains(variables['Build.SourceBranch'], 'tags')
...@@ -25,6 +25,7 @@ minimal_egg_info = textwrap.dedent(""" ...@@ -25,6 +25,7 @@ minimal_egg_info = textwrap.dedent("""
entry_points = setuptools.dist:check_entry_points entry_points = setuptools.dist:check_entry_points
[egg_info.writers] [egg_info.writers]
PKG-INFO = setuptools.command.egg_info:write_pkg_info
dependency_links.txt = setuptools.command.egg_info:overwrite_arg dependency_links.txt = setuptools.command.egg_info:overwrite_arg
entry_points.txt = setuptools.command.egg_info:write_entries entry_points.txt = setuptools.command.egg_info:write_entries
requires.txt = setuptools.command.egg_info:write_requirements requires.txt = setuptools.command.egg_info:write_requirements
...@@ -35,10 +36,11 @@ def ensure_egg_info(): ...@@ -35,10 +36,11 @@ def ensure_egg_info():
if os.path.exists('setuptools.egg-info'): if os.path.exists('setuptools.egg-info'):
return return
print("adding minimal entry_points") print("adding minimal entry_points")
build_egg_info() add_minimal_info()
run_egg_info()
def build_egg_info(): def add_minimal_info():
""" """
Build a minimal egg-info, enough to invoke egg_info Build a minimal egg-info, enough to invoke egg_info
""" """
...@@ -52,13 +54,6 @@ def run_egg_info(): ...@@ -52,13 +54,6 @@ def run_egg_info():
cmd = [sys.executable, 'setup.py', 'egg_info'] cmd = [sys.executable, 'setup.py', 'egg_info']
print("Regenerating egg_info") print("Regenerating egg_info")
subprocess.check_call(cmd) subprocess.check_call(cmd)
print("...and again.")
subprocess.check_call(cmd)
def main():
ensure_egg_info()
run_egg_info()
__name__ == '__main__' and main() __name__ == '__main__' and ensure_egg_info()
Add minimum sunset date and preamble to Python 2 warning.
=======================================
Build System Support
=======================================
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
uses a ``setup()`` function within the ``setup.py`` script. Commands such as
``python setup.py bdist`` or ``python setup.py bdist_wheel`` generate a
distribution bundle and ``python setup.py install`` installs the distribution.
This interface makes it difficult to choose other packaging tools without an
overhaul. Because ``setup.py`` scripts allowed for arbitrary execution, it
proved difficult to provide a reliable user experience across environments
and history.
`PEP 517 <https://www.python.org/dev/peps/pep-0517/>`_ therefore came to
rescue and specified a new standard to
package and distribute Python modules. Under PEP 517:
a ``pyproject.toml`` file is used to specify what program to use
for generating distribution.
Then, two functions provided by the program, ``build_wheel(directory: str)``
and ``build_sdist(directory: str)`` create the distribution bundle at the
specified ``directory``. The program is free to use its own configuration
script or extend the ``.toml`` file.
Lastly, ``pip install *.whl`` or ``pip install *.tar.gz`` does the actual
installation. If ``*.whl`` is available, ``pip`` will go ahead and copy
the files into ``site-packages`` directory. If not, ``pip`` will look at
``pyproject.toml`` and decide what program to use to 'build from source'
(the default is ``setuptools``)
With this standard, switching between packaging tools becomes a lot easier. ``build_meta``
implements ``setuptools``' build system support.
How to use it?
--------------
Starting with a package that you want to distribute. You will need your source
scripts, a ``pyproject.toml`` file and a ``setup.cfg`` file::
~/meowpkg/
pyproject.toml
setup.cfg
meowpkg/__init__.py
The pyproject.toml file is required to specify the build system (i.e. what is
being used to package your scripts and install from source). To use it with
setuptools, the content would be::
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
Use ``setuptools``' `declarative config`_ to specify the package information::
[metadata]
name = meowpkg
version = 0.0.1
description = a package that meows
[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::
$ pip install -q pep517
$ mkdir dist
$ python -m pep517.build .
And now it's done! The ``.whl`` file and ``.tar.gz`` can then be distributed
and installed::
dist/
meowpkg-0.0.1.whl
meowpkg-0.0.1.tar.gz
$ pip install dist/meowpkg-0.0.1.whl
or::
$ pip install dist/meowpkg-0.0.1.tar.gz
# -*- coding: utf-8 -*-
#
# Setuptools documentation build configuration file, created by
# sphinx-quickstart on Fri Jul 17 14:22:37 2009.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# The contents of this file are pickled, so don't put values in the namespace
# that aren't pickleable (module imports are okay, they're removed automatically).
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import subprocess import subprocess
import sys import sys
import os import os
...@@ -26,14 +6,12 @@ import os ...@@ -26,14 +6,12 @@ import os
# hack to run the bootstrap script so that jaraco.packaging.sphinx # hack to run the bootstrap script so that jaraco.packaging.sphinx
# can invoke setup.py # can invoke setup.py
'READTHEDOCS' in os.environ and subprocess.check_call( 'READTHEDOCS' in os.environ and subprocess.check_call(
[sys.executable, 'bootstrap.py'], [sys.executable, '-m', 'bootstrap'],
cwd=os.path.join(os.path.dirname(__file__), os.path.pardir), cwd=os.path.join(os.path.dirname(__file__), os.path.pardir),
) )
# -- General configuration ----------------------------------------------------- # -- General configuration --
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['jaraco.packaging.sphinx', 'rst.linker'] extensions = ['jaraco.packaging.sphinx', 'rst.linker']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
...@@ -45,7 +23,8 @@ source_suffix = '.txt' ...@@ -45,7 +23,8 @@ source_suffix = '.txt'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# A list of glob-style patterns that should be excluded when looking for source files. # A list of glob-style patterns that should be excluded
# when looking for source files.
exclude_patterns = ['requirements.txt'] exclude_patterns = ['requirements.txt']
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
...@@ -55,7 +34,7 @@ exclude_trees = [] ...@@ -55,7 +34,7 @@ exclude_trees = []
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output --
# The theme to use for HTML and HTML Help pages. Major themes that come with # The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'. # Sphinx are currently 'default' and 'sphinxdoc'.
...@@ -69,7 +48,10 @@ html_theme_path = ['_theme'] ...@@ -69,7 +48,10 @@ html_theme_path = ['_theme']
html_use_smartypants = True html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
html_sidebars = {'index': ['relations.html', 'sourcelink.html', 'indexsidebar.html', 'searchbox.html']} html_sidebars = {
'index': [
'relations.html', 'sourcelink.html', 'indexsidebar.html',
'searchbox.html']}
# If false, no module index is generated. # If false, no module index is generated.
html_use_modindex = False html_use_modindex = False
...@@ -77,14 +59,15 @@ html_use_modindex = False ...@@ -77,14 +59,15 @@ html_use_modindex = False
# If false, no index is generated. # If false, no index is generated.
html_use_index = False html_use_index = False
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author,
latex_documents = [ # documentclass [howto/manual]).
('index', 'Setuptools.tex', 'Setuptools Documentation', latex_documents = [(
'The fellowship of the packaging', 'manual'), 'index', 'Setuptools.tex', 'Setuptools Documentation',
] 'The fellowship of the packaging', 'manual',
)]
link_files = { link_files = {
'../CHANGES.rst': dict( '../CHANGES.rst': dict(
......
...@@ -104,12 +104,8 @@ from the command line after pushing a new branch. ...@@ -104,12 +104,8 @@ from the command line after pushing a new branch.
Testing Testing
------- -------
The primary tests are run using tox. To run the tests, first create the metadata The primary tests are run using tox. Make sure you have tox installed,
needed to run the tests:: and invoke it::
$ python bootstrap.py
Then make sure you have tox installed, and invoke it::
$ tox $ tox
......
...@@ -12,7 +12,7 @@ Credits ...@@ -12,7 +12,7 @@ Credits
* The original design for the ``.egg`` format and the ``pkg_resources`` API was * The original design for the ``.egg`` format and the ``pkg_resources`` API was
co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first co-created by Phillip Eby and Bob Ippolito. Bob also implemented the first
version of ``pkg_resources``, and supplied the OS X operating system version version of ``pkg_resources``, and supplied the macOS operating system version
compatibility algorithm. compatibility algorithm.
* Ian Bicking implemented many early "creature comfort" features of * Ian Bicking implemented many early "creature comfort" features of
......
...@@ -1621,7 +1621,7 @@ Platform Utilities ...@@ -1621,7 +1621,7 @@ Platform Utilities
``get_build_platform()`` ``get_build_platform()``
Return this platform's identifier string. For Windows, the return value Return this platform's identifier string. For Windows, the return value
is ``"win32"``, and for Mac OS X it is a string of the form is ``"win32"``, and for macOS it is a string of the form
``"macosx-10.4-ppc"``. All other platforms return the same uname-based ``"macosx-10.4-ppc"``. All other platforms return the same uname-based
string that the ``distutils.util.get_platform()`` function returns. string that the ``distutils.util.get_platform()`` function returns.
This string is the minimum platform version required by distributions built This string is the minimum platform version required by distributions built
...@@ -1641,7 +1641,7 @@ Platform Utilities ...@@ -1641,7 +1641,7 @@ Platform Utilities
considered a wildcard, and the platforms are therefore compatible. considered a wildcard, and the platforms are therefore compatible.
Likewise, if the platform strings are equal, they're also considered Likewise, if the platform strings are equal, they're also considered
compatible, and ``True`` is returned. Currently, the only non-equal compatible, and ``True`` is returned. Currently, the only non-equal
platform strings that are considered compatible are Mac OS X platform platform strings that are considered compatible are macOS platform
strings with the same hardware type (e.g. ``ppc``) and major version strings with the same hardware type (e.g. ``ppc``) and major version
(e.g. ``10``) with the `provided` platform's minor version being less than (e.g. ``10``) with the `provided` platform's minor version being less than
or equal to the `required` platform's minor version. or equal to the `required` platform's minor version.
...@@ -1674,7 +1674,7 @@ File/Path Utilities ...@@ -1674,7 +1674,7 @@ File/Path Utilities
the same filesystem location if they have equal ``normalized_path()`` the same filesystem location if they have equal ``normalized_path()``
values. Specifically, this is a shortcut for calling ``os.path.realpath`` values. Specifically, this is a shortcut for calling ``os.path.realpath``
and ``os.path.normcase`` on `path`. Unfortunately, on certain platforms and ``os.path.normcase`` on `path`. Unfortunately, on certain platforms
(notably Cygwin and Mac OS X) the ``normcase`` function does not accurately (notably Cygwin and macOS) the ``normcase`` function does not accurately
reflect the platform's case-sensitivity, so there is always the possibility reflect the platform's case-sensitivity, so there is always the possibility
of two apparently-different paths being equal on such platforms. of two apparently-different paths being equal on such platforms.
......
...@@ -3,39 +3,13 @@ Release Process ...@@ -3,39 +3,13 @@ Release Process
=============== ===============
In order to allow for rapid, predictable releases, Setuptools uses a In order to allow for rapid, predictable releases, Setuptools uses a
mechanical technique for releases, enacted by Travis following a mechanical technique for releases, enacted on tagged commits by
successful build of a tagged release per continuous integration.
`PyPI deployment <https://docs.travis-ci.com/user/deployment/pypi>`_.
To finalize a release, run ``tox -e finalize``, review, then push
Prior to cutting a release, please use `towncrier`_ to update the changes.
``CHANGES.rst`` to summarize the changes since the last release.
To update the changelog: If tests pass, the release will be uploaded to PyPI.
1. Install towncrier via ``pip install towncrier`` if not already installed.
2. Preview the new ``CHANGES.rst`` entry by running
``towncrier --draft --version {new.version.number}`` (enter the desired
version number for the next release). If any changes are needed, make
them and generate a new preview until the output is acceptable. Run
``git add`` for any modified files.
3. Run ``towncrier --version {new.version.number}`` to stage the changelog
updates in git.
4. Verify that there are no remaining ``changelog.d/*.rst`` files. If a
file was named incorrectly, it may be ignored by towncrier.
5. Review the updated ``CHANGES.rst`` file. If any changes are needed,
make the edits and stage them via ``git add CHANGES.rst``.
Once the changelog edits are staged and ready to commit, cut a release by
installing and running ``bump2version --allow-dirty {part}`` where ``part``
is major, minor, or patch based on the scope of the changes in the
release. Then, push the commits to the master branch::
$ git push origin master
$ git push --tags
If tests pass, the release will be uploaded to PyPI (from the Python 3.6
tests).
.. _towncrier: https://pypi.org/project/towncrier/
Release Frequency Release Frequency
----------------- -----------------
......
sphinx!=1.8.0 # keep these in sync with setup.cfg
pygments-github-lexers==0.0.5 sphinx
rst.linker>=1.9
jaraco.packaging>=6.1 jaraco.packaging>=6.1
rst.linker>=1.9
pygments-github-lexers==0.0.5
setuptools>=34 setuptools>=34
...@@ -88,7 +88,7 @@ packages in the directory where the setup.py lives. See the `Command ...@@ -88,7 +88,7 @@ packages in the directory where the setup.py lives. See the `Command
Reference`_ section below to see what commands you can give to this setup Reference`_ section below to see what commands you can give to this setup
script. For example, to produce a source distribution, simply invoke:: script. For example, to produce a source distribution, simply invoke::
python setup.py sdist setup.py sdist
Of course, before you release your project to PyPI, you'll want to add a bit 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 more information to your setup script to help people find or learn about your
...@@ -100,17 +100,17 @@ dependencies, and perhaps some data files and scripts:: ...@@ -100,17 +100,17 @@ dependencies, and perhaps some data files and scripts::
name="HelloWorld", name="HelloWorld",
version="0.1", version="0.1",
packages=find_packages(), packages=find_packages(),
scripts=['say_hello.py'], scripts=["say_hello.py"],
# Project uses reStructuredText, so ensure that the docutils get # Project uses reStructuredText, so ensure that the docutils get
# installed or upgraded on the target machine # installed or upgraded on the target machine
install_requires=['docutils>=0.3'], install_requires=["docutils>=0.3"],
package_data={ package_data={
# If any package contains *.txt or *.rst files, include them: # If any package contains *.txt or *.rst files, include them:
'': ['*.txt', '*.rst'], "": ["*.txt", "*.rst"],
# And include any *.msg files found in the 'hello' package, too: # And include any *.msg files found in the "hello" package, too:
'hello': ['*.msg'], "hello": ["*.msg"],
}, },
# metadata to display on PyPI # metadata to display on PyPI
...@@ -125,7 +125,7 @@ dependencies, and perhaps some data files and scripts:: ...@@ -125,7 +125,7 @@ dependencies, and perhaps some data files and scripts::
"Source Code": "https://code.example.com/HelloWorld/", "Source Code": "https://code.example.com/HelloWorld/",
}, },
classifiers=[ classifiers=[
'License :: OSI Approved :: Python Software Foundation License' "License :: OSI Approved :: Python Software Foundation License"
] ]
# could also include long_description, download_url, etc. # could also include long_description, download_url, etc.
...@@ -207,11 +207,11 @@ but here are a few tips that will keep you out of trouble in the corner cases: ...@@ -207,11 +207,11 @@ but here are a few tips that will keep you out of trouble in the corner cases:
to compare different version numbers:: to compare different version numbers::
>>> from pkg_resources import parse_version >>> from pkg_resources import parse_version
>>> parse_version('1.9.a.dev') == parse_version('1.9a0dev') >>> parse_version("1.9.a.dev") == parse_version("1.9a0dev")
True True
>>> parse_version('2.1-rc2') < parse_version('2.1') >>> parse_version("2.1-rc2") < parse_version("2.1")
True True
>>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9') >>> parse_version("0.6a9dev-r41475") < parse_version("0.6a9")
True True
Once you've decided on a version numbering scheme for your project, you can Once you've decided on a version numbering scheme for your project, you can
...@@ -371,7 +371,7 @@ unless you need the associated ``setuptools`` feature. ...@@ -371,7 +371,7 @@ unless you need the associated ``setuptools`` feature.
imported. This argument is only useful if the project will be installed as imported. This argument is only useful if the project will be installed as
a zipfile, and there is a need to have all of the listed resources be a zipfile, and there is a need to have all of the listed resources be
extracted to the filesystem *as a unit*. Resources listed here extracted to the filesystem *as a unit*. Resources listed here
should be '/'-separated paths, relative to the source root, so to list a should be "/"-separated paths, relative to the source root, so to list a
resource ``foo.png`` in package ``bar.baz``, you would include the string resource ``foo.png`` in package ``bar.baz``, you would include the string
``bar/baz/foo.png`` in this argument. ``bar/baz/foo.png`` in this argument.
...@@ -413,7 +413,7 @@ the same ...@@ -413,7 +413,7 @@ the same
directory as the setup script. Some projects use a ``src`` or ``lib`` directory as the setup script. Some projects use a ``src`` or ``lib``
directory as the root of their source tree, and those projects would of course directory as the root of their source tree, and those projects would of course
use ``"src"`` or ``"lib"`` as the first argument to ``find_packages()``. (And use ``"src"`` or ``"lib"`` as the first argument to ``find_packages()``. (And
such projects also need something like ``package_dir={'':'src'}`` in their such projects also need something like ``package_dir={"": "src"}`` in their
``setup()`` arguments, but that's just a normal distutils thing.) ``setup()`` arguments, but that's just a normal distutils thing.)
Anyway, ``find_packages()`` walks the target directory, filtering by inclusion Anyway, ``find_packages()`` walks the target directory, filtering by inclusion
...@@ -480,7 +480,7 @@ top-level package called ``tests``! One way to avoid this problem is to use the ...@@ -480,7 +480,7 @@ top-level package called ``tests``! One way to avoid this problem is to use the
setup( setup(
name="namespace.mypackage", name="namespace.mypackage",
version="0.1", version="0.1",
packages=find_namespace_packages(include=['namespace.*']) packages=find_namespace_packages(include=["namespace.*"])
) )
Another option is to use the "src" layout, where all package code is placed in Another option is to use the "src" layout, where all package code is placed in
...@@ -500,8 +500,8 @@ With this layout, the package directory is specified as ``src``, as such:: ...@@ -500,8 +500,8 @@ With this layout, the package directory is specified as ``src``, as such::
setup(name="namespace.mypackage", setup(name="namespace.mypackage",
version="0.1", version="0.1",
package_dir={'': 'src'}, package_dir={"": "src"},
packages=find_namespace_packages(where='src')) packages=find_namespace_packages(where="src"))
.. _PEP 420: https://www.python.org/dev/peps/pep-0420/ .. _PEP 420: https://www.python.org/dev/peps/pep-0420/
...@@ -526,12 +526,12 @@ script called ``baz``, you might do something like this:: ...@@ -526,12 +526,12 @@ script called ``baz``, you might do something like this::
setup( setup(
# other arguments here... # other arguments here...
entry_points={ entry_points={
'console_scripts': [ "console_scripts": [
'foo = my_package.some_module:main_func', "foo = my_package.some_module:main_func",
'bar = other_module:some_func', "bar = other_module:some_func",
], ],
'gui_scripts': [ "gui_scripts": [
'baz = my_package_gui:start_func', "baz = my_package_gui:start_func",
] ]
} }
) )
...@@ -560,6 +560,8 @@ Services and Plugins`_. ...@@ -560,6 +560,8 @@ Services and Plugins`_.
"Eggsecutable" Scripts "Eggsecutable" Scripts
---------------------- ----------------------
.. deprecated:: 45.3.0
Occasionally, there are situations where it's desirable to make an ``.egg`` Occasionally, there are situations where it's desirable to make an ``.egg``
file directly executable. You can do this by including an entry point such file directly executable. You can do this by including an entry point such
as the following:: as the following::
...@@ -567,8 +569,8 @@ as the following:: ...@@ -567,8 +569,8 @@ as the following::
setup( setup(
# other arguments here... # other arguments here...
entry_points={ entry_points={
'setuptools.installation': [ "setuptools.installation": [
'eggsecutable = my_package.some_module:main_func', "eggsecutable = my_package.some_module:main_func",
] ]
} }
) )
...@@ -741,8 +743,8 @@ For example, let's say that Project A offers optional PDF and reST support:: ...@@ -741,8 +743,8 @@ For example, let's say that Project A offers optional PDF and reST support::
name="Project-A", name="Project-A",
... ...
extras_require={ extras_require={
'PDF': ["ReportLab>=1.2", "RXP"], "PDF": ["ReportLab>=1.2", "RXP"],
'reST': ["docutils>=0.3"], "reST": ["docutils>=0.3"],
} }
) )
...@@ -763,9 +765,9 @@ declare it like this, so that the "PDF" requirements are only resolved if the ...@@ -763,9 +765,9 @@ declare it like this, so that the "PDF" requirements are only resolved if the
name="Project-A", name="Project-A",
... ...
entry_points={ entry_points={
'console_scripts': [ "console_scripts": [
'rst2pdf = project_a.tools.pdfgen [PDF]', "rst2pdf = project_a.tools.pdfgen [PDF]",
'rst2html = project_a.tools.htmlgen', "rst2html = project_a.tools.htmlgen",
# more script entry points ... # more script entry points ...
], ],
} }
...@@ -801,8 +803,8 @@ setup to this:: ...@@ -801,8 +803,8 @@ setup to this::
name="Project-A", name="Project-A",
... ...
extras_require={ extras_require={
'PDF': [], "PDF": [],
'reST': ["docutils>=0.3"], "reST": ["docutils>=0.3"],
} }
) )
...@@ -829,8 +831,8 @@ For example, here is a project that uses the ``enum`` module and ``pywin32``:: ...@@ -829,8 +831,8 @@ For example, here is a project that uses the ``enum`` module and ``pywin32``::
name="Project", name="Project",
... ...
install_requires=[ install_requires=[
'enum34;python_version<"3.4"', "enum34;python_version<'3.4'",
'pywin32 >= 1.0;platform_system=="Windows"' "pywin32 >= 1.0;platform_system=='Windows'"
] ]
) )
...@@ -878,9 +880,9 @@ e.g.:: ...@@ -878,9 +880,9 @@ e.g.::
... ...
package_data={ package_data={
# If any package contains *.txt or *.rst files, include them: # If any package contains *.txt or *.rst files, include them:
'': ['*.txt', '*.rst'], "": ["*.txt", "*.rst"],
# And include any *.msg files found in the 'hello' package, too: # And include any *.msg files found in the "hello" package, too:
'hello': ['*.msg'], "hello": ["*.msg"],
} }
) )
...@@ -903,15 +905,15 @@ The setuptools setup file might look like this:: ...@@ -903,15 +905,15 @@ The setuptools setup file might look like this::
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup( setup(
... ...
packages=find_packages('src'), # include all packages under src packages=find_packages("src"), # include all packages under src
package_dir={'':'src'}, # tell distutils packages are under src package_dir={"": "src"}, # tell distutils packages are under src
package_data={ package_data={
# If any package contains *.txt files, include them: # If any package contains *.txt files, include them:
'': ['*.txt'], "": ["*.txt"],
# And include any *.dat files found in the 'data' subdirectory # And include any *.dat files found in the "data" subdirectory
# of the 'mypkg' package, also: # of the "mypkg" package, also:
'mypkg': ['data/*.dat'], "mypkg": ["data/*.dat"],
} }
) )
...@@ -926,7 +928,7 @@ converts slashes to appropriate platform-specific separators at build time. ...@@ -926,7 +928,7 @@ converts slashes to appropriate platform-specific separators at build time.
If datafiles are contained in a subdirectory of a package that isn't a package If datafiles are contained in a subdirectory of a package that isn't a package
itself (no ``__init__.py``), then the subdirectory names (or ``*``) are required itself (no ``__init__.py``), then the subdirectory names (or ``*``) are required
in the ``package_data`` argument (as shown above with ``'data/*.dat'``). in the ``package_data`` argument (as shown above with ``"data/*.dat"``).
When building an ``sdist``, the datafiles are also drawn from the When building an ``sdist``, the datafiles are also drawn from the
``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if ``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if
...@@ -939,7 +941,7 @@ python.org website. If using the setuptools-specific ``include_package_data`` ...@@ -939,7 +941,7 @@ python.org website. If using the setuptools-specific ``include_package_data``
argument, files specified by ``package_data`` will *not* be automatically argument, files specified by ``package_data`` will *not* be automatically
added to the manifest unless they are listed in the MANIFEST.in file.) added to the manifest unless they are listed in the MANIFEST.in file.)
__ http://docs.python.org/dist/node11.html __ https://docs.python.org/3/distutils/setupscript.html#installing-package-data
Sometimes, the ``include_package_data`` or ``package_data`` options alone Sometimes, the ``include_package_data`` or ``package_data`` options alone
aren't sufficient to precisely define what files you want included. For aren't sufficient to precisely define what files you want included. For
...@@ -951,18 +953,18 @@ to do things like this:: ...@@ -951,18 +953,18 @@ to do things like this::
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup( setup(
... ...
packages=find_packages('src'), # include all packages under src packages=find_packages("src"), # include all packages under src
package_dir={'':'src'}, # tell distutils packages are under src package_dir={"": "src"}, # tell distutils packages are under src
include_package_data=True, # include everything in source control include_package_data=True, # include everything in source control
# ...but exclude README.txt from all packages # ...but exclude README.txt from all packages
exclude_package_data={'': ['README.txt']}, exclude_package_data={"": ["README.txt"]},
) )
The ``exclude_package_data`` option is a dictionary mapping package names to The ``exclude_package_data`` option is a dictionary mapping package names to
lists of wildcard patterns, just like the ``package_data`` option. And, just lists of wildcard patterns, just like the ``package_data`` option. And, just
as with that option, a key of ``''`` will apply the given pattern(s) to all as with that option, a key of ``""`` will apply the given pattern(s) to all
packages. However, any files that match these patterns will be *excluded* packages. However, any files that match these patterns will be *excluded*
from installation, even if they were listed in ``package_data`` or were from installation, even if they were listed in ``package_data`` or were
included as a result of using ``include_package_data``. included as a result of using ``include_package_data``.
...@@ -1096,12 +1098,12 @@ for our hypothetical blogging tool:: ...@@ -1096,12 +1098,12 @@ for our hypothetical blogging tool::
setup( setup(
# ... # ...
entry_points={'blogtool.parsers': '.rst = some_module:SomeClass'} entry_points={"blogtool.parsers": ".rst = some_module:SomeClass"}
) )
setup( setup(
# ... # ...
entry_points={'blogtool.parsers': ['.rst = some_module:a_func']} entry_points={"blogtool.parsers": [".rst = some_module:a_func"]}
) )
setup( setup(
...@@ -1220,7 +1222,7 @@ Before you begin, make sure you have the latest versions of setuptools and wheel ...@@ -1220,7 +1222,7 @@ Before you begin, make sure you have the latest versions of setuptools and wheel
To build a setuptools project, run this command from the same directory where To build a setuptools project, run this command from the same directory where
setup.py is located:: setup.py is located::
python3 setup.py sdist bdist_wheel setup.py sdist bdist_wheel
This will generate distribution archives in the `dist` directory. This will generate distribution archives in the `dist` directory.
...@@ -1309,7 +1311,7 @@ participates in. For example, the ZopeInterface project might do this:: ...@@ -1309,7 +1311,7 @@ participates in. For example, the ZopeInterface project might do this::
setup( setup(
# ... # ...
namespace_packages=['zope'] namespace_packages=["zope"]
) )
because it contains a ``zope.interface`` package that lives in the ``zope`` because it contains a ``zope.interface`` package that lives in the ``zope``
...@@ -1327,7 +1329,7 @@ packages' ``__init__.py`` files (and the ``__init__.py`` of any parent ...@@ -1327,7 +1329,7 @@ packages' ``__init__.py`` files (and the ``__init__.py`` of any parent
packages), in a normal Python package layout. These ``__init__.py`` files packages), in a normal Python package layout. These ``__init__.py`` files
*must* contain the line:: *must* contain the line::
__import__('pkg_resources').declare_namespace(__name__) __import__("pkg_resources").declare_namespace(__name__)
This code ensures that the namespace package machinery is operating and that This code ensures that the namespace package machinery is operating and that
the current package is registered as a namespace package. the current package is registered as a namespace package.
...@@ -1410,7 +1412,7 @@ pattern. So, you can use a command line like:: ...@@ -1410,7 +1412,7 @@ pattern. So, you can use a command line like::
setup.py egg_info -rbDEV bdist_egg rotate -m.egg -k3 setup.py egg_info -rbDEV bdist_egg rotate -m.egg -k3
to build an egg whose version info includes 'DEV-rNNNN' (where NNNN is the to build an egg whose version info includes "DEV-rNNNN" (where NNNN is the
most recent Subversion revision that affected the source tree), and then most recent Subversion revision that affected the source tree), and then
delete any egg files from the distribution directory except for the three delete any egg files from the distribution directory except for the three
that were built most recently. that were built most recently.
...@@ -1469,7 +1471,7 @@ tagging the release, so the trunk will still produce development snapshots. ...@@ -1469,7 +1471,7 @@ tagging the release, so the trunk will still produce development snapshots.
Alternately, if you are not branching for releases, you can override the Alternately, if you are not branching for releases, you can override the
default version options on the command line, using something like:: default version options on the command line, using something like::
python setup.py egg_info -Db "" sdist bdist_egg setup.py egg_info -Db "" sdist bdist_egg
The first part of this command (``egg_info -Db ""``) will override the The first part of this command (``egg_info -Db ""``) will override the
configured tag information, before creating source and binary eggs. Thus, these configured tag information, before creating source and binary eggs. Thus, these
...@@ -1479,11 +1481,11 @@ build designation string. ...@@ -1479,11 +1481,11 @@ build designation string.
Of course, if you will be doing this a lot, you may wish to create a personal Of course, if you will be doing this a lot, you may wish to create a personal
alias for this operation, e.g.:: alias for this operation, e.g.::
python setup.py alias -u release egg_info -Db "" setup.py alias -u release egg_info -Db ""
You can then use it like this:: You can then use it like this::
python setup.py release sdist bdist_egg setup.py release sdist bdist_egg
Or of course you can create more elaborate aliases that do all of the above. Or of course you can create more elaborate aliases that do all of the above.
See the sections below on the `egg_info`_ and `alias`_ commands for more ideas. See the sections below on the `egg_info`_ and `alias`_ commands for more ideas.
...@@ -1500,7 +1502,7 @@ To ensure Cython is available, include Cython in the build-requires section ...@@ -1500,7 +1502,7 @@ To ensure Cython is available, include Cython in the build-requires section
of your pyproject.toml:: of your pyproject.toml::
[build-system] [build-system]
requires=[..., 'cython'] requires=[..., "cython"]
Built with pip 10 or later, that declaration is sufficient to include Cython Built with pip 10 or later, that declaration is sufficient to include Cython
in the build. For broader compatibility, declare the dependency in your in the build. For broader compatibility, declare the dependency in your
...@@ -1800,7 +1802,7 @@ to support "daily builds" or "snapshot" releases. It is run automatically by ...@@ -1800,7 +1802,7 @@ to support "daily builds" or "snapshot" releases. It is run automatically by
the ``sdist``, ``bdist_egg``, ``develop``, and ``test`` commands in order to the ``sdist``, ``bdist_egg``, ``develop``, and ``test`` commands in order to
update the project's metadata, but you can also specify it explicitly in order update the project's metadata, but you can also specify it explicitly in order
to temporarily change the project's version string while executing other to temporarily change the project's version string while executing other
commands. (It also generates the``.egg-info/SOURCES.txt`` manifest file, which commands. (It also generates the ``.egg-info/SOURCES.txt`` manifest file, which
is used when you are building source distributions.) is used when you are building source distributions.)
In addition to writing the core egg metadata defined by ``setuptools`` and In addition to writing the core egg metadata defined by ``setuptools`` and
...@@ -1848,7 +1850,7 @@ binary distributions of your project, you should first make sure that you know ...@@ -1848,7 +1850,7 @@ binary distributions of your project, you should first make sure that you know
how the resulting version numbers will be interpreted by automated tools how the resulting version numbers will be interpreted by automated tools
like pip. See the section above on `Specifying Your Project's Version`_ for an like pip. See the section above on `Specifying Your Project's Version`_ for an
explanation of pre- and post-release tags, as well as tips on how to choose and explanation of pre- and post-release tags, as well as tips on how to choose and
verify a versioning scheme for your your project.) verify a versioning scheme for your project.)
For advanced uses, there is one other option that can be set, to change the For advanced uses, there is one other option that can be set, to change the
location of the project's ``.egg-info`` directory. Commands that need to find location of the project's ``.egg-info`` directory. Commands that need to find
...@@ -1873,12 +1875,12 @@ Other ``egg_info`` Options ...@@ -1873,12 +1875,12 @@ Other ``egg_info`` Options
Creating a dated "nightly build" snapshot egg:: Creating a dated "nightly build" snapshot egg::
python setup.py egg_info --tag-date --tag-build=DEV bdist_egg setup.py egg_info --tag-date --tag-build=DEV bdist_egg
Creating a release with no version tags, even if some default tags are Creating a release with no version tags, even if some default tags are
specified in ``setup.cfg``:: specified in ``setup.cfg``::
python setup.py egg_info -RDb "" sdist bdist_egg setup.py egg_info -RDb "" sdist bdist_egg
(Notice that ``egg_info`` must always appear on the command line *before* any (Notice that ``egg_info`` must always appear on the command line *before* any
commands that you want the version changes to apply to.) commands that you want the version changes to apply to.)
...@@ -2104,8 +2106,9 @@ Configuring setup() using setup.cfg files ...@@ -2104,8 +2106,9 @@ Configuring setup() using setup.cfg files
.. note:: New in 30.3.0 (8 Dec 2016). .. note:: New in 30.3.0 (8 Dec 2016).
.. important:: .. important::
A ``setup.py`` file containing a ``setup()`` function call is still If compatibility with legacy builds (i.e. those not using the :pep:`517`
required even if your configuration resides in ``setup.cfg``. build API) is desired, a ``setup.py`` file containing a ``setup()`` function
call is still required even if your configuration resides in ``setup.cfg``.
``Setuptools`` allows using configuration files (usually :file:`setup.cfg`) ``Setuptools`` allows using configuration files (usually :file:`setup.cfg`)
to define a package’s metadata and other options that are normally supplied to define a package’s metadata and other options that are normally supplied
...@@ -2394,7 +2397,7 @@ parsing ``metadata`` and ``options`` sections into a dictionary. ...@@ -2394,7 +2397,7 @@ parsing ``metadata`` and ``options`` sections into a dictionary.
from setuptools.config import read_configuration from setuptools.config import read_configuration
conf_dict = read_configuration('/home/user/dev/package/setup.cfg') conf_dict = read_configuration("/home/user/dev/package/setup.cfg")
By default, ``read_configuration()`` will read only the file provided By default, ``read_configuration()`` will read only the file provided
...@@ -2574,7 +2577,7 @@ a file. Here's what the writer utility looks like:: ...@@ -2574,7 +2577,7 @@ a file. Here's what the writer utility looks like::
argname = os.path.splitext(basename)[0] argname = os.path.splitext(basename)[0]
value = getattr(cmd.distribution, argname, None) value = getattr(cmd.distribution, argname, None)
if value is not None: if value is not None:
value = '\n'.join(value) + '\n' value = "\n".join(value) + "\n"
cmd.write_or_delete_file(argname, filename, value) cmd.write_or_delete_file(argname, filename, value)
As you can see, ``egg_info.writers`` entry points must be a function taking As you can see, ``egg_info.writers`` entry points must be a function taking
......
# Configuration for pull request documentation previews via Netlify # Configuration for pull request documentation previews via Netlify
# Netlify relies on there being a ./runtime.txt to indicate Python 3.
[build] [build]
publish = "docs/build/html" publish = "build/html"
command = "pip install tox && tox -e docs" command = "pip install tox && tox -e docs"
...@@ -55,7 +55,7 @@ except NameError: ...@@ -55,7 +55,7 @@ except NameError:
FileExistsError = OSError FileExistsError = OSError
from pkg_resources.extern import six from pkg_resources.extern import six
from pkg_resources.extern.six.moves import urllib, map, filter from pkg_resources.extern.six.moves import map, filter
# capture these to bypass sandboxing # capture these to bypass sandboxing
from os import utime from os import utime
...@@ -179,10 +179,10 @@ def get_supported_platform(): ...@@ -179,10 +179,10 @@ def get_supported_platform():
"""Return this platform's maximum compatible version. """Return this platform's maximum compatible version.
distutils.util.get_platform() normally reports the minimum version distutils.util.get_platform() normally reports the minimum version
of Mac OS X that would be required to *use* extensions produced by of macOS that would be required to *use* extensions produced by
distutils. But what we want when checking compatibility is to know the distutils. But what we want when checking compatibility is to know the
version of Mac OS X that we are *running*. To allow usage of packages that version of macOS that we are *running*. To allow usage of packages that
explicitly require a newer version of Mac OS X, we must also know the explicitly require a newer version of macOS, we must also know the
current version of the OS. current version of the OS.
If this condition occurs for any other platform with a version in its If this condition occurs for any other platform with a version in its
...@@ -192,9 +192,9 @@ def get_supported_platform(): ...@@ -192,9 +192,9 @@ def get_supported_platform():
m = macosVersionString.match(plat) m = macosVersionString.match(plat)
if m is not None and sys.platform == "darwin": if m is not None and sys.platform == "darwin":
try: try:
plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3)) plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
except ValueError: except ValueError:
# not Mac OS X # not macOS
pass pass
return plat return plat
...@@ -365,7 +365,7 @@ def get_provider(moduleOrReq): ...@@ -365,7 +365,7 @@ def get_provider(moduleOrReq):
return _find_adapter(_provider_factories, loader)(module) return _find_adapter(_provider_factories, loader)(module)
def _macosx_vers(_cache=[]): def _macos_vers(_cache=[]):
if not _cache: if not _cache:
version = platform.mac_ver()[0] version = platform.mac_ver()[0]
# fallback for MacPorts # fallback for MacPorts
...@@ -381,7 +381,7 @@ def _macosx_vers(_cache=[]): ...@@ -381,7 +381,7 @@ def _macosx_vers(_cache=[]):
return _cache[0] return _cache[0]
def _macosx_arch(machine): def _macos_arch(machine):
return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
...@@ -389,18 +389,18 @@ def get_build_platform(): ...@@ -389,18 +389,18 @@ def get_build_platform():
"""Return this platform's string for platform-specific distributions """Return this platform's string for platform-specific distributions
XXX Currently this is the same as ``distutils.util.get_platform()``, but it XXX Currently this is the same as ``distutils.util.get_platform()``, but it
needs some hacks for Linux and Mac OS X. needs some hacks for Linux and macOS.
""" """
from sysconfig import get_platform from sysconfig import get_platform
plat = get_platform() plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'): if sys.platform == "darwin" and not plat.startswith('macosx-'):
try: try:
version = _macosx_vers() version = _macos_vers()
machine = os.uname()[4].replace(" ", "_") machine = os.uname()[4].replace(" ", "_")
return "macosx-%d.%d-%s" % ( return "macosx-%d.%d-%s" % (
int(version[0]), int(version[1]), int(version[0]), int(version[1]),
_macosx_arch(machine), _macos_arch(machine),
) )
except ValueError: except ValueError:
# if someone is running a non-Mac darwin system, this will fall # if someone is running a non-Mac darwin system, this will fall
...@@ -426,7 +426,7 @@ def compatible_platforms(provided, required): ...@@ -426,7 +426,7 @@ def compatible_platforms(provided, required):
# easy case # easy case
return True return True
# Mac OS X special cases # macOS special cases
reqMac = macosVersionString.match(required) reqMac = macosVersionString.match(required)
if reqMac: if reqMac:
provMac = macosVersionString.match(provided) provMac = macosVersionString.match(provided)
...@@ -435,7 +435,7 @@ def compatible_platforms(provided, required): ...@@ -435,7 +435,7 @@ def compatible_platforms(provided, required):
if not provMac: if not provMac:
# this is backwards compatibility for packages built before # this is backwards compatibility for packages built before
# setuptools 0.6. All packages built after this point will # setuptools 0.6. All packages built after this point will
# use the new macosx designation. # use the new macOS designation.
provDarwin = darwinVersionString.match(provided) provDarwin = darwinVersionString.match(provided)
if provDarwin: if provDarwin:
dversion = int(provDarwin.group(1)) dversion = int(provDarwin.group(1))
...@@ -443,7 +443,7 @@ def compatible_platforms(provided, required): ...@@ -443,7 +443,7 @@ def compatible_platforms(provided, required):
if dversion == 7 and macosversion >= "10.3" or \ if dversion == 7 and macosversion >= "10.3" or \
dversion == 8 and macosversion >= "10.4": dversion == 8 and macosversion >= "10.4":
return True return True
# egg isn't macosx or legacy darwin # egg isn't macOS or legacy darwin
return False return False
# are they the same major version and machine type? # are they the same major version and machine type?
...@@ -1235,12 +1235,13 @@ class ResourceManager: ...@@ -1235,12 +1235,13 @@ class ResourceManager:
mode = os.stat(path).st_mode mode = os.stat(path).st_mode
if mode & stat.S_IWOTH or mode & stat.S_IWGRP: if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
msg = ( msg = (
"%s is writable by group/others and vulnerable to attack " "Extraction path is writable by group/others "
"when " "and vulnerable to attack when "
"used with get_resource_filename. Consider a more secure " "used with get_resource_filename ({path}). "
"Consider a more secure "
"location (set with .set_extraction_path or the " "location (set with .set_extraction_path or the "
"PYTHON_EGG_CACHE environment variable)." % path "PYTHON_EGG_CACHE environment variable)."
) ).format(**locals())
warnings.warn(msg, UserWarning) warnings.warn(msg, UserWarning)
def postprocess(self, tempname, filename): def postprocess(self, tempname, filename):
...@@ -2068,11 +2069,14 @@ def find_on_path(importer, path_item, only=False): ...@@ -2068,11 +2069,14 @@ def find_on_path(importer, path_item, only=False):
def dist_factory(path_item, entry, only): def dist_factory(path_item, entry, only):
""" """Return a dist_factory for the given entry."""
Return a dist_factory for a path_item and entry
"""
lower = entry.lower() lower = entry.lower()
is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info'))) is_egg_info = lower.endswith('.egg-info')
is_dist_info = (
lower.endswith('.dist-info') and
os.path.isdir(os.path.join(path_item, entry))
)
is_meta = is_egg_info or is_dist_info
return ( return (
distributions_from_metadata distributions_from_metadata
if is_meta else if is_meta else
...@@ -2196,10 +2200,14 @@ def _handle_ns(packageName, path_item): ...@@ -2196,10 +2200,14 @@ def _handle_ns(packageName, path_item):
if importer is None: if importer is None:
return None return None
# capture warnings due to #1111 # use find_spec (PEP 451) and fall-back to find_module (PEP 302)
with warnings.catch_warnings(): try:
warnings.simplefilter("ignore") loader = importer.find_spec(packageName).loader
loader = importer.find_module(packageName) except AttributeError:
# capture warnings due to #1111
with warnings.catch_warnings():
warnings.simplefilter("ignore")
loader = importer.find_module(packageName)
if loader is None: if loader is None:
return None return None
...@@ -2329,7 +2337,8 @@ register_namespace_handler(object, null_ns_handler) ...@@ -2329,7 +2337,8 @@ register_namespace_handler(object, null_ns_handler)
def normalize_path(filename): def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes""" """Normalize a file/dir name for comparison purposes"""
return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) return os.path.normcase(os.path.realpath(os.path.normpath(
_cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover def _cygwin_patch(filename): # pragma: nocover
...@@ -2537,15 +2546,6 @@ class EntryPoint: ...@@ -2537,15 +2546,6 @@ class EntryPoint:
return maps return maps
def _remove_md5_fragment(location):
if not location:
return ''
parsed = urllib.parse.urlparse(location)
if parsed[-1].startswith('md5='):
return urllib.parse.urlunparse(parsed[:-1] + ('',))
return location
def _version_from_file(lines): def _version_from_file(lines):
""" """
Given an iterable of lines from a Metadata file, return Given an iterable of lines from a Metadata file, return
...@@ -2602,7 +2602,7 @@ class Distribution: ...@@ -2602,7 +2602,7 @@ class Distribution:
self.parsed_version, self.parsed_version,
self.precedence, self.precedence,
self.key, self.key,
_remove_md5_fragment(self.location), self.location,
self.py_version or '', self.py_version or '',
self.platform or '', self.platform or '',
) )
...@@ -3288,6 +3288,7 @@ def _initialize_master_working_set(): ...@@ -3288,6 +3288,7 @@ def _initialize_master_working_set():
list(map(working_set.add_entry, sys.path)) list(map(working_set.add_entry, sys.path))
globals().update(locals()) globals().update(locals())
class PkgResourcesDeprecationWarning(Warning): class PkgResourcesDeprecationWarning(Warning):
""" """
Base class for warning about deprecations in ``pkg_resources`` Base class for warning about deprecations in ``pkg_resources``
......
...@@ -290,8 +290,8 @@ Platform Compatibility Rules ...@@ -290,8 +290,8 @@ Platform Compatibility Rules
---------------------------- ----------------------------
On the Mac, there are potential compatibility issues for modules compiled On the Mac, there are potential compatibility issues for modules compiled
on newer versions of Mac OS X than what the user is running. Additionally, on newer versions of macOS than what the user is running. Additionally,
Mac OS X will soon have two platforms to contend with: Intel and PowerPC. macOS will soon have two platforms to contend with: Intel and PowerPC.
Basic equality works as on other platforms:: Basic equality works as on other platforms::
......
...@@ -43,13 +43,6 @@ class VendorImporter: ...@@ -43,13 +43,6 @@ class VendorImporter:
__import__(extant) __import__(extant)
mod = sys.modules[extant] mod = sys.modules[extant]
sys.modules[fullname] = mod sys.modules[fullname] = mod
# mysterious hack:
# Remove the reference to the extant package/module
# on later Python versions to cause relative imports
# in the vendor package to resolve the same modules
# as those going through this importer.
if prefix and sys.version_info > (3, 3):
del sys.modules[extant]
return mod return mod
except ImportError: except ImportError:
pass pass
......
...@@ -12,9 +12,8 @@ msg = textwrap.dedent(""" ...@@ -12,9 +12,8 @@ msg = textwrap.dedent("""
Setuptools using pip 9.x or later or pin to `setuptools<45` Setuptools using pip 9.x or later or pin to `setuptools<45`
in your environment. in your environment.
If you have done those things and are still encountering If you have done those things and are still encountering
this message, please comment in this message, please follow up at
https://github.com/pypa/setuptools/issues/1458 https://bit.ly/setuptools-py2-warning.
about the steps that led to this unsupported combination.
""") """)
pre = "Setuptools will stop working on Python 2\n" pre = "Setuptools will stop working on Python 2\n"
......
...@@ -17,7 +17,9 @@ try: ...@@ -17,7 +17,9 @@ try:
except ImportError: except ImportError:
import mock import mock
from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution from pkg_resources import (
DistInfoDistribution, Distribution, EggInfoDistribution,
)
from setuptools.extern import six from setuptools.extern import six
from pkg_resources.extern.six.moves import map from pkg_resources.extern.six.moves import map
from pkg_resources.extern.six import text_type, string_types from pkg_resources.extern.six import text_type, string_types
...@@ -279,8 +281,8 @@ def make_distribution_no_version(tmpdir, basename): ...@@ -279,8 +281,8 @@ def make_distribution_no_version(tmpdir, basename):
('dist-info', 'METADATA', DistInfoDistribution), ('dist-info', 'METADATA', DistInfoDistribution),
], ],
) )
def test_distribution_version_missing(tmpdir, suffix, expected_filename, def test_distribution_version_missing(
expected_dist_type): tmpdir, suffix, expected_filename, expected_dist_type):
""" """
Test Distribution.version when the "Version" header is missing. Test Distribution.version when the "Version" header is missing.
""" """
...@@ -328,6 +330,14 @@ def test_distribution_version_missing_undetected_path(): ...@@ -328,6 +330,14 @@ def test_distribution_version_missing_undetected_path():
assert msg == expected assert msg == expected
@pytest.mark.parametrize('only', [False, True])
def test_dist_info_is_not_dir(tmp_path, only):
"""Test path containing a file with dist-info extension."""
dist_info = tmp_path / 'foobar.dist-info'
dist_info.touch()
assert not pkg_resources.dist_factory(str(tmp_path), str(dist_info), only)
class TestDeepVersionLookupDistutils: class TestDeepVersionLookupDistutils:
@pytest.fixture @pytest.fixture
def env(self, tmpdir): def env(self, tmpdir):
......
...@@ -15,7 +15,7 @@ import pkg_resources ...@@ -15,7 +15,7 @@ import pkg_resources
from pkg_resources import ( from pkg_resources import (
parse_requirements, VersionConflict, parse_version, parse_requirements, VersionConflict, parse_version,
Distribution, EntryPoint, Requirement, safe_version, safe_name, Distribution, EntryPoint, Requirement, safe_version, safe_name,
WorkingSet, PkgResourcesDeprecationWarning) WorkingSet)
# from Python 3.6 docs. # from Python 3.6 docs.
...@@ -501,7 +501,6 @@ class TestEntryPoints: ...@@ -501,7 +501,6 @@ class TestEntryPoints:
ep.load(require=False) ep.load(require=False)
class TestRequirements: class TestRequirements:
def testBasics(self): def testBasics(self):
r = Requirement.parse("Twisted>=1.2") r = Requirement.parse("Twisted>=1.2")
......
...@@ -14,8 +14,8 @@ __metaclass__ = type ...@@ -14,8 +14,8 @@ __metaclass__ = type
def strip_comments(s): def strip_comments(s):
return '\n'.join( return '\n'.join(
l for l in s.split('\n') line for line in s.split('\n')
if l.strip() and not l.strip().startswith('#') if line.strip() and not line.strip().startswith('#')
) )
......
[build-system] [build-system]
requires = ["setuptools >= 40.8", "wheel"] requires = [
# avoid self install on Python 2; ref #1996
"setuptools >= 40.8; python_version > '3'",
"wheel",
]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
backend-path = ["."] backend-path = ["."]
......
[pytest] [pytest]
addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX addopts=--doctest-modules --flake8 --doctest-glob=pkg_resources/api_tests.txt -r sxX
norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .* norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .* setuptools/_vendor pkg_resources/_vendor
flake8-ignore =
setuptools/site-patch.py F821
setuptools/py*compat.py F811
doctest_optionflags=ELLIPSIS ALLOW_UNICODE doctest_optionflags=ELLIPSIS ALLOW_UNICODE
filterwarnings = filterwarnings =
# https://github.com/pypa/setuptools/issues/1823 # Fail on warnings
ignore:bdist_wininst command is deprecated error
# https://github.com/pypa/setuptools/issues/1823
ignore:bdist_wininst command is deprecated
# Suppress this error; unimportant for CI tests
ignore:Extraction path is writable by group/others:UserWarning
# Suppress Python 2 deprecation warning
ignore:Setuptools will stop working on Python 2:UserWarning
# Suppress weird RuntimeWarning.
ignore:Parent module 'setuptools' not found while handling absolute import:RuntimeWarning
# Suppress use of bytes for filenames on Windows until fixed #2016
ignore:The Windows bytes API has been deprecated:DeprecationWarning
# Suppress other Python 2 UnicodeWarnings
ignore:Unicode equal comparison failed to convert:UnicodeWarning
ignore:Unicode unequal comparison failed to convert:UnicodeWarning
# https://github.com/pypa/setuptools/issues/2025
ignore:direct construction of .*Item has been deprecated:DeprecationWarning
# https://github.com/pypa/setuptools/issues/2081
ignore:lib2to3 package is deprecated:PendingDeprecationWarning
ignore:lib2to3 package is deprecated:DeprecationWarning
...@@ -14,12 +14,9 @@ repository = https://upload.pypi.org/legacy/ ...@@ -14,12 +14,9 @@ repository = https://upload.pypi.org/legacy/
[sdist] [sdist]
formats = zip formats = zip
[bdist_wheel]
universal = 1
[metadata] [metadata]
name = setuptools name = setuptools
version = 45.0.0 version = 46.3.1
description = Easily download, build, install, upgrade, and uninstall Python packages description = Easily download, build, install, upgrade, and uninstall Python packages
author = Python Packaging Authority author = Python Packaging Authority
author_email = distutils-sig@python.org author_email = distutils-sig@python.org
...@@ -36,6 +33,7 @@ classifiers = ...@@ -36,6 +33,7 @@ classifiers =
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Operating System :: OS Independent Operating System :: OS Independent
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
...@@ -57,5 +55,27 @@ exclude = *.tests ...@@ -57,5 +55,27 @@ exclude = *.tests
[options.extras_require] [options.extras_require]
ssl = ssl =
wincertstore==0.2; sys_platform=='win32' wincertstore==0.2; sys_platform=='win32'
certs = 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
pytest-cov>=2.5.1
paver; python_version>="3.6"
futures; python_version=="2.7"
pip>=19.1 # For proper file:// URLs support.
docs =
# Keep these in sync with docs/requirements.txt
sphinx
jaraco.packaging>=6.1
rst.linker>=1.9
pygments-github-lexers==0.0.5
...@@ -91,7 +91,6 @@ setup_params = dict( ...@@ -91,7 +91,6 @@ setup_params = dict(
], ],
"setuptools.finalize_distribution_options": [ "setuptools.finalize_distribution_options": [
"parent_finalize = setuptools.dist:_Distribution.finalize_options", "parent_finalize = setuptools.dist:_Distribution.finalize_options",
"features = setuptools.dist:Distribution._finalize_feature_opts",
"keywords = setuptools.dist:Distribution._finalize_setup_keywords", "keywords = setuptools.dist:Distribution._finalize_setup_keywords",
"2to3_doctests = " "2to3_doctests = "
"setuptools.dist:Distribution._finalize_2to3_doctests", "setuptools.dist:Distribution._finalize_2to3_doctests",
......
"""Extensions to the 'distutils' for large or complex distributions""" """Extensions to the 'distutils' for large or complex distributions"""
import os import os
import sys
import functools import functools
import distutils.core import distutils.core
import distutils.filelist import distutils.filelist
...@@ -17,7 +16,7 @@ from setuptools.extern.six.moves import filter, map ...@@ -17,7 +16,7 @@ from setuptools.extern.six.moves import filter, map
import setuptools.version import setuptools.version
from setuptools.extension import Extension from setuptools.extension import Extension
from setuptools.dist import Distribution, Feature from setuptools.dist import Distribution
from setuptools.depends import Require from setuptools.depends import Require
from . import monkey from . import monkey
...@@ -25,13 +24,13 @@ __metaclass__ = type ...@@ -25,13 +24,13 @@ __metaclass__ = type
__all__ = [ __all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'setup', 'Distribution', 'Command', 'Extension', 'Require',
'SetuptoolsDeprecationWarning', 'SetuptoolsDeprecationWarning',
'find_packages' 'find_packages'
] ]
if PY3: if PY3:
__all__.append('find_namespace_packages') __all__.append('find_namespace_packages')
__version__ = setuptools.version.__version__ __version__ = setuptools.version.__version__
...@@ -123,7 +122,7 @@ class PEP420PackageFinder(PackageFinder): ...@@ -123,7 +122,7 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find find_packages = PackageFinder.find
if PY3: if PY3:
find_namespace_packages = PEP420PackageFinder.find find_namespace_packages = PEP420PackageFinder.find
def _install_setup_requires(attrs): def _install_setup_requires(attrs):
...@@ -144,6 +143,7 @@ def setup(**attrs): ...@@ -144,6 +143,7 @@ def setup(**attrs):
_install_setup_requires(attrs) _install_setup_requires(attrs)
return distutils.core.setup(**attrs) return distutils.core.setup(**attrs)
setup.__doc__ = distutils.core.setup.__doc__ setup.__doc__ = distutils.core.setup.__doc__
...@@ -191,8 +191,8 @@ class Command(_Command): ...@@ -191,8 +191,8 @@ class Command(_Command):
ok = False ok = False
if not ok: if not ok:
raise DistutilsOptionError( raise DistutilsOptionError(
"'%s' must be a list of strings (got %r)" "'%s' must be a list of strings (got %r)"
% (option, val)) % (option, val))
def reinitialize_command(self, command, reinit_subcommands=0, **kw): def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands) cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
...@@ -224,5 +224,9 @@ def findall(dir=os.curdir): ...@@ -224,5 +224,9 @@ def findall(dir=os.curdir):
return list(files) return list(files)
class sic(str):
"""Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
# Apply monkey patches # Apply monkey patches
monkey.patch_all() monkey.patch_all()
...@@ -17,9 +17,18 @@ C_BUILTIN = 6 ...@@ -17,9 +17,18 @@ C_BUILTIN = 6
PY_FROZEN = 7 PY_FROZEN = 7
def find_spec(module, paths):
finder = (
importlib.machinery.PathFinder().find_spec
if isinstance(paths, list) else
importlib.util.find_spec
)
return finder(module, paths)
def find_module(module, paths=None): def find_module(module, paths=None):
"""Just like 'imp.find_module()', but with package support""" """Just like 'imp.find_module()', but with package support"""
spec = importlib.util.find_spec(module, paths) spec = find_spec(module, paths)
if spec is None: if spec is None:
raise ImportError("Can't find %s" % module) raise ImportError("Can't find %s" % module)
if not spec.has_location and hasattr(spec, 'submodule_search_locations'): if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
...@@ -60,14 +69,14 @@ def find_module(module, paths=None): ...@@ -60,14 +69,14 @@ def find_module(module, paths=None):
def get_frozen_object(module, paths=None): def get_frozen_object(module, paths=None):
spec = importlib.util.find_spec(module, paths) spec = find_spec(module, paths)
if not spec: if not spec:
raise ImportError("Can't find %s" % module) raise ImportError("Can't find %s" % module)
return spec.loader.get_code(module) return spec.loader.get_code(module)
def get_module(module, paths, info): def get_module(module, paths, info):
spec = importlib.util.find_spec(module, paths) spec = find_spec(module, paths)
if not spec: if not spec:
raise ImportError("Can't find %s" % module) raise ImportError("Can't find %s" % module)
return module_from_spec(spec) return module_from_spec(spec)
...@@ -25,7 +25,8 @@ def default_filter(src, dst): ...@@ -25,7 +25,8 @@ def default_filter(src, dst):
return dst return dst
def unpack_archive(filename, extract_dir, progress_filter=default_filter, def unpack_archive(
filename, extract_dir, progress_filter=default_filter,
drivers=None): drivers=None):
"""Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat``
...@@ -148,7 +149,8 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): ...@@ -148,7 +149,8 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
# resolve any links and to extract the link targets as normal # resolve any links and to extract the link targets as normal
# files # files
while member is not None and (member.islnk() or member.issym()): while member is not None and (
member.islnk() or member.issym()):
linkpath = member.linkname linkpath = member.linkname
if member.issym(): if member.issym():
base = posixpath.dirname(member.name) base = posixpath.dirname(member.name)
......
...@@ -48,6 +48,7 @@ __all__ = ['get_requires_for_build_sdist', ...@@ -48,6 +48,7 @@ __all__ = ['get_requires_for_build_sdist',
'__legacy__', '__legacy__',
'SetupRequirementsError'] 'SetupRequirementsError']
class SetupRequirementsError(BaseException): class SetupRequirementsError(BaseException):
def __init__(self, specifiers): def __init__(self, specifiers):
self.specifiers = specifiers self.specifiers = specifiers
...@@ -143,7 +144,8 @@ class _BuildMetaBackend(object): ...@@ -143,7 +144,8 @@ class _BuildMetaBackend(object):
def get_requires_for_build_wheel(self, config_settings=None): def get_requires_for_build_wheel(self, config_settings=None):
config_settings = self._fix_config(config_settings) config_settings = self._fix_config(config_settings)
return self._get_build_requires(config_settings, requirements=['wheel']) return self._get_build_requires(
config_settings, requirements=['wheel'])
def get_requires_for_build_sdist(self, config_settings=None): def get_requires_for_build_sdist(self, config_settings=None):
config_settings = self._fix_config(config_settings) config_settings = self._fix_config(config_settings)
...@@ -160,8 +162,10 @@ class _BuildMetaBackend(object): ...@@ -160,8 +162,10 @@ class _BuildMetaBackend(object):
dist_infos = [f for f in os.listdir(dist_info_directory) dist_infos = [f for f in os.listdir(dist_info_directory)
if f.endswith('.dist-info')] if f.endswith('.dist-info')]
if (len(dist_infos) == 0 and if (
len(_get_immediate_subdirectories(dist_info_directory)) == 1): len(dist_infos) == 0 and
len(_get_immediate_subdirectories(dist_info_directory)) == 1
):
dist_info_directory = os.path.join( dist_info_directory = os.path.join(
dist_info_directory, os.listdir(dist_info_directory)[0]) dist_info_directory, os.listdir(dist_info_directory)[0])
...@@ -193,7 +197,8 @@ class _BuildMetaBackend(object): ...@@ -193,7 +197,8 @@ class _BuildMetaBackend(object):
config_settings["--global-option"]) config_settings["--global-option"])
self.run_setup() self.run_setup()
result_basename = _file_with_extension(tmp_dist_dir, result_extension) result_basename = _file_with_extension(
tmp_dist_dir, result_extension)
result_path = os.path.join(result_directory, result_basename) result_path = os.path.join(result_directory, result_basename)
if os.path.exists(result_path): if os.path.exists(result_path):
# os.rename will fail overwriting on non-Unix. # os.rename will fail overwriting on non-Unix.
...@@ -202,7 +207,6 @@ class _BuildMetaBackend(object): ...@@ -202,7 +207,6 @@ class _BuildMetaBackend(object):
return result_basename return result_basename
def build_wheel(self, wheel_directory, config_settings=None, def build_wheel(self, wheel_directory, config_settings=None,
metadata_directory=None): metadata_directory=None):
return self._build_with_temp_dir(['bdist_wheel'], '.whl', return self._build_with_temp_dir(['bdist_wheel'], '.whl',
...@@ -217,9 +221,12 @@ class _BuildMetaBackend(object): ...@@ -217,9 +221,12 @@ class _BuildMetaBackend(object):
class _BuildMetaLegacyBackend(_BuildMetaBackend): class _BuildMetaLegacyBackend(_BuildMetaBackend):
"""Compatibility backend for setuptools """Compatibility backend for setuptools
This is a version of setuptools.build_meta that endeavors to maintain backwards This is a version of setuptools.build_meta that endeavors
compatibility with pre-PEP 517 modes of invocation. It exists as a temporary to maintain backwards
bridge between the old packaging mechanism and the new packaging mechanism, compatibility with pre-PEP 517 modes of invocation. It
exists as a temporary
bridge between the old packaging mechanism and the new
packaging mechanism,
and will eventually be removed. and will eventually be removed.
""" """
def run_setup(self, setup_script='setup.py'): def run_setup(self, setup_script='setup.py'):
...@@ -232,6 +239,12 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): ...@@ -232,6 +239,12 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend):
if script_dir not in sys.path: if script_dir not in sys.path:
sys.path.insert(0, script_dir) sys.path.insert(0, script_dir)
# Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
# get the directory of the source code. They expect it to refer to the
# setup.py script.
sys_argv_0 = sys.argv[0]
sys.argv[0] = setup_script
try: try:
super(_BuildMetaLegacyBackend, super(_BuildMetaLegacyBackend,
self).run_setup(setup_script=setup_script) self).run_setup(setup_script=setup_script)
...@@ -242,6 +255,8 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): ...@@ -242,6 +255,8 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend):
# the original path so that the path manipulation does not persist # the original path so that the path manipulation does not persist
# within the hook after run_setup is called. # within the hook after run_setup is called.
sys.path[:] = sys_path sys.path[:] = sys_path
sys.argv[0] = sys_argv_0
# The primary backend # The primary backend
_BACKEND = _BuildMetaBackend() _BACKEND = _BuildMetaBackend()
......
...@@ -11,13 +11,14 @@ import os ...@@ -11,13 +11,14 @@ import os
import re import re
import textwrap import textwrap
import marshal import marshal
import warnings
from setuptools.extern import six from setuptools.extern import six
from pkg_resources import get_build_platform, Distribution, ensure_directory from pkg_resources import get_build_platform, Distribution, ensure_directory
from pkg_resources import EntryPoint from pkg_resources import EntryPoint
from setuptools.extension import Library from setuptools.extension import Library
from setuptools import Command from setuptools import Command, SetuptoolsDeprecationWarning
try: try:
# Python 2.7 or >=3.2 # Python 2.7 or >=3.2
...@@ -278,6 +279,12 @@ class bdist_egg(Command): ...@@ -278,6 +279,12 @@ class bdist_egg(Command):
if ep is None: if ep is None:
return 'w' # not an eggsecutable, do it the usual way. 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: if not ep.attrs or ep.extras:
raise DistutilsSetupError( raise DistutilsSetupError(
"eggsecutable entry point (%r) cannot have 'extras' " "eggsecutable entry point (%r) cannot have 'extras' "
......
import distutils.command.bdist_wininst as orig import distutils.command.bdist_wininst as orig
import warnings
from setuptools import SetuptoolsDeprecationWarning
class bdist_wininst(orig.bdist_wininst): class bdist_wininst(orig.bdist_wininst):
...@@ -14,6 +17,12 @@ class bdist_wininst(orig.bdist_wininst): ...@@ -14,6 +17,12 @@ class bdist_wininst(orig.bdist_wininst):
return cmd return cmd
def run(self): 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 self._is_running = True
try: try:
orig.bdist_wininst.run(self) orig.bdist_wininst.run(self)
......
...@@ -25,9 +25,9 @@ class build_clib(orig.build_clib): ...@@ -25,9 +25,9 @@ class build_clib(orig.build_clib):
sources = build_info.get('sources') sources = build_info.get('sources')
if sources is None or not isinstance(sources, (list, tuple)): if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError( raise DistutilsSetupError(
"in 'libraries' option (library '%s'), " "in 'libraries' option (library '%s'), "
"'sources' must be present and must be " "'sources' must be present and must be "
"a list of source filenames" % lib_name) "a list of source filenames" % lib_name)
sources = list(sources) sources = list(sources)
log.info("building '%s' library", lib_name) log.info("building '%s' library", lib_name)
...@@ -38,9 +38,9 @@ class build_clib(orig.build_clib): ...@@ -38,9 +38,9 @@ class build_clib(orig.build_clib):
obj_deps = build_info.get('obj_deps', dict()) obj_deps = build_info.get('obj_deps', dict())
if not isinstance(obj_deps, dict): if not isinstance(obj_deps, dict):
raise DistutilsSetupError( raise DistutilsSetupError(
"in 'libraries' option (library '%s'), " "in 'libraries' option (library '%s'), "
"'obj_deps' must be a dictionary of " "'obj_deps' must be a dictionary of "
"type 'source: list'" % lib_name) "type 'source: list'" % lib_name)
dependencies = [] dependencies = []
# Get the global dependencies that are specified by the '' key. # Get the global dependencies that are specified by the '' key.
...@@ -48,9 +48,9 @@ class build_clib(orig.build_clib): ...@@ -48,9 +48,9 @@ class build_clib(orig.build_clib):
global_deps = obj_deps.get('', list()) global_deps = obj_deps.get('', list())
if not isinstance(global_deps, (list, tuple)): if not isinstance(global_deps, (list, tuple)):
raise DistutilsSetupError( raise DistutilsSetupError(
"in 'libraries' option (library '%s'), " "in 'libraries' option (library '%s'), "
"'obj_deps' must be a dictionary of " "'obj_deps' must be a dictionary of "
"type 'source: list'" % lib_name) "type 'source: list'" % lib_name)
# Build the list to be used by newer_pairwise_group # Build the list to be used by newer_pairwise_group
# each source will be auto-added to its dependencies. # each source will be auto-added to its dependencies.
...@@ -60,39 +60,42 @@ class build_clib(orig.build_clib): ...@@ -60,39 +60,42 @@ class build_clib(orig.build_clib):
extra_deps = obj_deps.get(source, list()) extra_deps = obj_deps.get(source, list())
if not isinstance(extra_deps, (list, tuple)): if not isinstance(extra_deps, (list, tuple)):
raise DistutilsSetupError( raise DistutilsSetupError(
"in 'libraries' option (library '%s'), " "in 'libraries' option (library '%s'), "
"'obj_deps' must be a dictionary of " "'obj_deps' must be a dictionary of "
"type 'source: list'" % lib_name) "type 'source: list'" % lib_name)
src_deps.extend(extra_deps) src_deps.extend(extra_deps)
dependencies.append(src_deps) dependencies.append(src_deps)
expected_objects = self.compiler.object_filenames( expected_objects = self.compiler.object_filenames(
sources, sources,
output_dir=self.build_temp output_dir=self.build_temp,
) )
if newer_pairwise_group(dependencies, expected_objects) != ([], []): if (
newer_pairwise_group(dependencies, expected_objects)
!= ([], [])
):
# First, compile the source code to object files in the library # First, compile the source code to object files in the library
# directory. (This should probably change to putting object # directory. (This should probably change to putting object
# files in a temporary build directory.) # files in a temporary build directory.)
macros = build_info.get('macros') macros = build_info.get('macros')
include_dirs = build_info.get('include_dirs') include_dirs = build_info.get('include_dirs')
cflags = build_info.get('cflags') cflags = build_info.get('cflags')
objects = self.compiler.compile( self.compiler.compile(
sources, sources,
output_dir=self.build_temp, output_dir=self.build_temp,
macros=macros, macros=macros,
include_dirs=include_dirs, include_dirs=include_dirs,
extra_postargs=cflags, extra_postargs=cflags,
debug=self.debug debug=self.debug
) )
# Now "link" the object files together into a static library. # Now "link" the object files together into a static library.
# (On Unix at least, this isn't really linking -- it just # (On Unix at least, this isn't really linking -- it just
# builds an archive. Whatever.) # builds an archive. Whatever.)
self.compiler.create_static_lib( self.compiler.create_static_lib(
expected_objects, expected_objects,
lib_name, lib_name,
output_dir=self.build_clib, output_dir=self.build_clib,
debug=self.debug debug=self.debug
) )
...@@ -14,7 +14,8 @@ from setuptools.extern import six ...@@ -14,7 +14,8 @@ from setuptools.extern import six
if six.PY2: if six.PY2:
import imp import imp
EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] EXTENSION_SUFFIXES = [
s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION]
else: else:
from importlib.machinery import EXTENSION_SUFFIXES from importlib.machinery import EXTENSION_SUFFIXES
...@@ -29,7 +30,7 @@ except ImportError: ...@@ -29,7 +30,7 @@ except ImportError:
# make sure _config_vars is initialized # make sure _config_vars is initialized
get_config_var("LDSHARED") get_config_var("LDSHARED")
from distutils.sysconfig import _config_vars as _CONFIG_VARS from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa
def _customize_compiler_for_shlib(compiler): def _customize_compiler_for_shlib(compiler):
...@@ -65,7 +66,9 @@ elif os.name != 'nt': ...@@ -65,7 +66,9 @@ elif os.name != 'nt':
except ImportError: except ImportError:
pass pass
if_dl = lambda s: s if have_rtld else ''
def if_dl(s):
return s if have_rtld else ''
def get_abi3_suffix(): def get_abi3_suffix():
......
...@@ -7,6 +7,7 @@ import textwrap ...@@ -7,6 +7,7 @@ import textwrap
import io import io
import distutils.errors import distutils.errors
import itertools import itertools
import stat
from setuptools.extern import six from setuptools.extern import six
from setuptools.extern.six.moves import map, filter, filterfalse from setuptools.extern.six.moves import map, filter, filterfalse
...@@ -20,6 +21,10 @@ except ImportError: ...@@ -20,6 +21,10 @@ except ImportError:
"do nothing" "do nothing"
def make_writable(target):
os.chmod(target, os.stat(target).st_mode | stat.S_IWRITE)
class build_py(orig.build_py, Mixin2to3): class build_py(orig.build_py, Mixin2to3):
"""Enhanced 'build_py' command that includes data files with packages """Enhanced 'build_py' command that includes data files with packages
...@@ -121,6 +126,7 @@ class build_py(orig.build_py, Mixin2to3): ...@@ -121,6 +126,7 @@ class build_py(orig.build_py, Mixin2to3):
self.mkpath(os.path.dirname(target)) self.mkpath(os.path.dirname(target))
srcfile = os.path.join(src_dir, filename) srcfile = os.path.join(src_dir, filename)
outf, copied = self.copy_file(srcfile, target) outf, copied = self.copy_file(srcfile, target)
make_writable(target)
srcfile = os.path.abspath(srcfile) srcfile = os.path.abspath(srcfile)
if (copied and if (copied and
srcfile in self.distribution.convert_2to3_doctests): srcfile in self.distribution.convert_2to3_doctests):
......
#!/usr/bin/env python
""" """
Easy Install Easy Install
------------ ------------
...@@ -121,7 +120,8 @@ else: ...@@ -121,7 +120,8 @@ else:
return False return False
_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ') def _one_liner(text):
return textwrap.dedent(text).strip().replace('\n', '; ')
class easy_install(Command): class easy_install(Command):
...@@ -156,19 +156,16 @@ class easy_install(Command): ...@@ -156,19 +156,16 @@ class easy_install(Command):
"allow building eggs from local checkouts"), "allow building eggs from local checkouts"),
('version', None, "print version information and exit"), ('version', None, "print version information and exit"),
('no-find-links', None, ('no-find-links', None,
"Don't load find-links defined in packages being installed") "Don't load find-links defined in packages being installed"),
('user', None, "install in user site-package '%s'" % site.USER_SITE)
] ]
boolean_options = [ boolean_options = [
'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
'editable', 'editable',
'no-deps', 'local-snapshots-ok', 'version' 'no-deps', 'local-snapshots-ok', 'version',
'user'
] ]
if site.ENABLE_USER_SITE:
help_msg = "install in user site-package '%s'" % site.USER_SITE
user_options.append(('user', None, help_msg))
boolean_options.append('user')
negative_opt = {'always-unzip': 'zip-ok'} negative_opt = {'always-unzip': 'zip-ok'}
create_index = PackageIndex create_index = PackageIndex
...@@ -272,6 +269,9 @@ class easy_install(Command): ...@@ -272,6 +269,9 @@ class easy_install(Command):
self.config_vars['userbase'] = self.install_userbase self.config_vars['userbase'] = self.install_userbase
self.config_vars['usersite'] = self.install_usersite self.config_vars['usersite'] = self.install_usersite
elif self.user:
log.warn("WARNING: The user site-packages directory is disabled.")
self._fix_install_dir_for_user_site() self._fix_install_dir_for_user_site()
self.expand_basedirs() self.expand_basedirs()
...@@ -414,8 +414,8 @@ class easy_install(Command): ...@@ -414,8 +414,8 @@ class easy_install(Command):
if show_deprecation: if show_deprecation:
self.announce( self.announce(
"WARNING: The easy_install command is deprecated " "WARNING: The easy_install command is deprecated "
"and will be removed in a future version." "and will be removed in a future version.",
, log.WARN, log.WARN,
) )
if self.verbose != self.distribution.verbose: if self.verbose != self.distribution.verbose:
log.set_verbosity(self.verbose) log.set_verbosity(self.verbose)
...@@ -459,6 +459,12 @@ class easy_install(Command): ...@@ -459,6 +459,12 @@ class easy_install(Command):
instdir = normalize_path(self.install_dir) instdir = normalize_path(self.install_dir)
pth_file = os.path.join(instdir, 'easy-install.pth') pth_file = os.path.join(instdir, 'easy-install.pth')
if not os.path.exists(instdir):
try:
os.makedirs(instdir)
except (OSError, IOError):
self.cant_write_to_target()
# Is it a configured, PYTHONPATH, implicit, or explicit site dir? # Is it a configured, PYTHONPATH, implicit, or explicit site dir?
is_site_dir = instdir in self.all_site_dirs is_site_dir = instdir in self.all_site_dirs
...@@ -478,8 +484,9 @@ class easy_install(Command): ...@@ -478,8 +484,9 @@ class easy_install(Command):
self.cant_write_to_target() self.cant_write_to_target()
if not is_site_dir and not self.multi_version: if not is_site_dir and not self.multi_version:
# Can't install non-multi to non-site dir # Can't install non-multi to non-site dir with easy_install
raise DistutilsError(self.no_default_version_msg()) pythonpath = os.environ.get('PYTHONPATH', '')
log.warn(self.__no_default_msg, self.install_dir, pythonpath)
if is_site_dir: if is_site_dir:
if self.pth_file is None: if self.pth_file is None:
...@@ -507,13 +514,13 @@ class easy_install(Command): ...@@ -507,13 +514,13 @@ class easy_install(Command):
the distutils default setting) was: the distutils default setting) was:
%s %s
""").lstrip() """).lstrip() # noqa
__not_exists_id = textwrap.dedent(""" __not_exists_id = textwrap.dedent("""
This directory does not currently exist. Please create it and try again, or This directory does not currently exist. Please create it and try again, or
choose a different installation directory (using the -d or --install-dir choose a different installation directory (using the -d or --install-dir
option). option).
""").lstrip() """).lstrip() # noqa
__access_msg = textwrap.dedent(""" __access_msg = textwrap.dedent("""
Perhaps your account does not have write access to this directory? If the Perhaps your account does not have write access to this directory? If the
...@@ -529,7 +536,7 @@ class easy_install(Command): ...@@ -529,7 +536,7 @@ class easy_install(Command):
https://setuptools.readthedocs.io/en/latest/easy_install.html https://setuptools.readthedocs.io/en/latest/easy_install.html
Please make the appropriate changes for your system and try again. Please make the appropriate changes for your system and try again.
""").lstrip() """).lstrip() # noqa
def cant_write_to_target(self): def cant_write_to_target(self):
msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)
...@@ -1093,13 +1100,13 @@ class easy_install(Command): ...@@ -1093,13 +1100,13 @@ class easy_install(Command):
pkg_resources.require("%(name)s") # latest installed version pkg_resources.require("%(name)s") # latest installed version
pkg_resources.require("%(name)s==%(version)s") # this exact version pkg_resources.require("%(name)s==%(version)s") # this exact version
pkg_resources.require("%(name)s>=%(version)s") # this version or higher pkg_resources.require("%(name)s>=%(version)s") # this version or higher
""").lstrip() """).lstrip() # noqa
__id_warning = textwrap.dedent(""" __id_warning = textwrap.dedent("""
Note also that the installation directory must be on sys.path at runtime for Note also that the installation directory must be on sys.path at runtime for
this to work. (e.g. by being the application's script directory, by being on this to work. (e.g. by being the application's script directory, by being on
PYTHONPATH, or by being added to sys.path by your code.) PYTHONPATH, or by being added to sys.path by your code.)
""") """) # noqa
def installation_report(self, req, dist, what="Installed"): def installation_report(self, req, dist, what="Installed"):
"""Helpful installation message for display to package users""" """Helpful installation message for display to package users"""
...@@ -1124,7 +1131,7 @@ class easy_install(Command): ...@@ -1124,7 +1131,7 @@ class easy_install(Command):
%(python)s setup.py develop %(python)s setup.py develop
See the setuptools documentation for the "develop" command for more info. See the setuptools documentation for the "develop" command for more info.
""").lstrip() """).lstrip() # noqa
def report_editable(self, spec, setup_script): def report_editable(self, spec, setup_script):
dirname = os.path.dirname(setup_script) dirname = os.path.dirname(setup_script)
...@@ -1307,11 +1314,8 @@ class easy_install(Command): ...@@ -1307,11 +1314,8 @@ class easy_install(Command):
https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations
Please make the appropriate changes for your system and try again.""").lstrip() Please make the appropriate changes for your system and try again.
""").strip()
def no_default_version_msg(self):
template = self.__no_default_msg
return template % (self.install_dir, os.environ.get('PYTHONPATH', ''))
def install_site_py(self): def install_site_py(self):
"""Make sure there's a site.py in the target dir, if needed""" """Make sure there's a site.py in the target dir, if needed"""
...@@ -2093,7 +2097,8 @@ class ScriptWriter: ...@@ -2093,7 +2097,8 @@ class ScriptWriter:
@classmethod @classmethod
def get_script_header(cls, script_text, executable=None, wininst=False): def get_script_header(cls, script_text, executable=None, wininst=False):
# for backward compatibility # for backward compatibility
warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2) warnings.warn(
"Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
if wininst: if wininst:
executable = "python.exe" executable = "python.exe"
return cls.get_header(script_text, executable) return cls.get_header(script_text, executable)
...@@ -2342,6 +2347,8 @@ def _patch_usage(): ...@@ -2342,6 +2347,8 @@ def _patch_usage():
finally: finally:
distutils.core.gen_usage = saved distutils.core.gen_usage = saved
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
"""Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" """
Warning for EasyInstall deprecations, bypassing suppression.
"""
...@@ -33,6 +33,7 @@ from setuptools.glob import glob ...@@ -33,6 +33,7 @@ from setuptools.glob import glob
from setuptools.extern import packaging from setuptools.extern import packaging
from setuptools import SetuptoolsDeprecationWarning from setuptools import SetuptoolsDeprecationWarning
def translate_pattern(glob): def translate_pattern(glob):
""" """
Translate a file path glob like '*.txt' in to a regular expression. Translate a file path glob like '*.txt' in to a regular expression.
...@@ -113,7 +114,7 @@ def translate_pattern(glob): ...@@ -113,7 +114,7 @@ def translate_pattern(glob):
pat += sep pat += sep
pat += r'\Z' pat += r'\Z'
return re.compile(pat, flags=re.MULTILINE|re.DOTALL) return re.compile(pat, flags=re.MULTILINE | re.DOTALL)
class InfoCommon: class InfoCommon:
...@@ -637,7 +638,9 @@ def warn_depends_obsolete(cmd, basename, filename): ...@@ -637,7 +638,9 @@ def warn_depends_obsolete(cmd, basename, filename):
def _write_requirements(stream, reqs): def _write_requirements(stream, reqs):
lines = yield_lines(reqs or ()) lines = yield_lines(reqs or ())
append_cr = lambda line: line + '\n'
def append_cr(line):
return line + '\n'
lines = map(append_cr, lines) lines = map(append_cr, lines)
stream.writelines(lines) stream.writelines(lines)
...@@ -703,7 +706,8 @@ def get_pkg_info_revision(): ...@@ -703,7 +706,8 @@ def get_pkg_info_revision():
Get a -r### off of PKG-INFO Version in case this is an sdist of Get a -r### off of PKG-INFO Version in case this is an sdist of
a subversion revision. a subversion revision.
""" """
warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) warnings.warn(
"get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
if os.path.exists('PKG-INFO'): if os.path.exists('PKG-INFO'):
with io.open('PKG-INFO') as f: with io.open('PKG-INFO') as f:
for line in f: for line in f:
...@@ -714,4 +718,4 @@ def get_pkg_info_revision(): ...@@ -714,4 +718,4 @@ def get_pkg_info_revision():
class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
"""Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning.""" """Deprecated behavior warning for EggInfo, bypassing suppression."""
...@@ -77,7 +77,8 @@ class install_lib(orig.install_lib): ...@@ -77,7 +77,8 @@ class install_lib(orig.install_lib):
if not hasattr(sys, 'implementation'): if not hasattr(sys, 'implementation'):
return return
base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag) base = os.path.join(
'__pycache__', '__init__.' + sys.implementation.cache_tag)
yield base + '.pyc' yield base + '.pyc'
yield base + '.pyo' yield base + '.pyo'
yield base + '.opt-1.pyc' yield base + '.opt-1.pyc'
......
...@@ -32,8 +32,11 @@ class install_scripts(orig.install_scripts): ...@@ -32,8 +32,11 @@ class install_scripts(orig.install_scripts):
) )
bs_cmd = self.get_finalized_command('build_scripts') bs_cmd = self.get_finalized_command('build_scripts')
exec_param = getattr(bs_cmd, 'executable', None) exec_param = getattr(bs_cmd, 'executable', None)
bw_cmd = self.get_finalized_command("bdist_wininst") try:
is_wininst = getattr(bw_cmd, '_is_running', False) bw_cmd = self.get_finalized_command("bdist_wininst")
is_wininst = getattr(bw_cmd, '_is_running', False)
except ImportError:
is_wininst = False
writer = ei.ScriptWriter writer = ei.ScriptWriter
if is_wininst: if is_wininst:
exec_param = "python.exe" exec_param = "python.exe"
......
...@@ -132,5 +132,5 @@ class sdist_add_defaults: ...@@ -132,5 +132,5 @@ class sdist_add_defaults:
if hasattr(sdist.sdist, '_add_defaults_standards'): if hasattr(sdist.sdist, '_add_defaults_standards'):
# disable the functionality already available upstream # disable the functionality already available upstream
class sdist_add_defaults: class sdist_add_defaults: # noqa
pass pass
...@@ -129,7 +129,8 @@ class test(Command): ...@@ -129,7 +129,8 @@ class test(Command):
@contextlib.contextmanager @contextlib.contextmanager
def project_on_sys_path(self, include_dists=[]): def project_on_sys_path(self, include_dists=[]):
with_2to3 = not six.PY2 and getattr(self.distribution, 'use_2to3', False) with_2to3 = not six.PY2 and getattr(
self.distribution, 'use_2to3', False)
if with_2to3: if with_2to3:
# If we run 2to3 we can not do this inplace: # If we run 2to3 we can not do this inplace:
......
...@@ -127,8 +127,8 @@ class upload_docs(upload): ...@@ -127,8 +127,8 @@ class upload_docs(upload):
""" """
Build up the MIME payload for the POST data Build up the MIME payload for the POST data
""" """
boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = b'\n--' + boundary sep_boundary = b'\n--' + boundary.encode('ascii')
end_boundary = sep_boundary + b'--' end_boundary = sep_boundary + b'--'
end_items = end_boundary, b"\n", end_items = end_boundary, b"\n",
builder = functools.partial( builder = functools.partial(
...@@ -138,7 +138,7 @@ class upload_docs(upload): ...@@ -138,7 +138,7 @@ class upload_docs(upload):
part_groups = map(builder, data.items()) part_groups = map(builder, data.items())
parts = itertools.chain.from_iterable(part_groups) parts = itertools.chain.from_iterable(part_groups)
body_items = itertools.chain(parts, end_items) body_items = itertools.chain(parts, end_items)
content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii') content_type = 'multipart/form-data; boundary=%s' % boundary
return b''.join(body_items), content_type return b''.join(body_items), content_type
def upload_file(self, filename): def upload_file(self, filename):
......
from distutils.dep_util import newer_group from distutils.dep_util import newer_group
# yes, this is was almost entirely copy-pasted from # yes, this is was almost entirely copy-pasted from
# 'newer_pairwise()', this is just another convenience # 'newer_pairwise()', this is just another convenience
# function. # function.
...@@ -10,7 +11,8 @@ def newer_pairwise_group(sources_groups, targets): ...@@ -10,7 +11,8 @@ def newer_pairwise_group(sources_groups, targets):
of 'newer_group()'. of 'newer_group()'.
""" """
if len(sources_groups) != len(targets): if len(sources_groups) != len(targets):
raise ValueError("'sources_group' and 'targets' must be the same length") raise ValueError(
"'sources_group' and 'targets' must be the same length")
# build a pair of lists (sources_groups, targets) where source is newer # build a pair of lists (sources_groups, targets) where source is newer
n_sources = [] n_sources = []
......
...@@ -19,9 +19,7 @@ import itertools ...@@ -19,9 +19,7 @@ import itertools
from collections import defaultdict from collections import defaultdict
from email import message_from_file from email import message_from_file
from distutils.errors import ( from distutils.errors import DistutilsOptionError, DistutilsSetupError
DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError,
)
from distutils.util import rfc822_escape from distutils.util import rfc822_escape
from distutils.version import StrictVersion from distutils.version import StrictVersion
...@@ -32,7 +30,7 @@ from setuptools.extern.six.moves import map, filter, filterfalse ...@@ -32,7 +30,7 @@ from setuptools.extern.six.moves import map, filter, filterfalse
from . import SetuptoolsDeprecationWarning from . import SetuptoolsDeprecationWarning
from setuptools.depends import Require import setuptools
from setuptools import windows_support from setuptools import windows_support
from setuptools.monkey import get_unpatched from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration from setuptools.config import parse_configuration
...@@ -162,7 +160,7 @@ def write_pkg_file(self, file): ...@@ -162,7 +160,7 @@ def write_pkg_file(self, file):
if self.download_url: if self.download_url:
write_field('Download-URL', self.download_url) write_field('Download-URL', self.download_url)
for project_url in self.project_urls.items(): for project_url in self.project_urls.items():
write_field('Project-URL', '%s, %s' % project_url) write_field('Project-URL', '%s, %s' % project_url)
long_desc = rfc822_escape(self.get_long_description()) long_desc = rfc822_escape(self.get_long_description())
write_field('Description', long_desc) write_field('Description', long_desc)
...@@ -338,7 +336,7 @@ _Distribution = get_unpatched(distutils.core.Distribution) ...@@ -338,7 +336,7 @@ _Distribution = get_unpatched(distutils.core.Distribution)
class Distribution(_Distribution): class Distribution(_Distribution):
"""Distribution with support for features, tests, and package data """Distribution with support for tests and package data
This is an enhanced version of 'distutils.dist.Distribution' that This is an enhanced version of 'distutils.dist.Distribution' that
effectively adds the following new optional keyword arguments to 'setup()': effectively adds the following new optional keyword arguments to 'setup()':
...@@ -365,21 +363,6 @@ class Distribution(_Distribution): ...@@ -365,21 +363,6 @@ class Distribution(_Distribution):
EasyInstall and requests one of your extras, the corresponding EasyInstall and requests one of your extras, the corresponding
additional requirements will be installed if needed. additional requirements will be installed if needed.
'features' **deprecated** -- a dictionary mapping option names to
'setuptools.Feature'
objects. Features are a portion of the distribution that can be
included or excluded based on user options, inter-feature dependencies,
and availability on the current system. Excluded features are omitted
from all setup commands, including source and binary distributions, so
you can create multiple distributions from the same source tree.
Feature names should be valid Python identifiers, except that they may
contain the '-' (minus) sign. Features can be included or excluded
via the command line options '--with-X' and '--without-X', where 'X' is
the name of the feature. Whether a feature is included by default, and
whether you are allowed to control this from the command line, is
determined by the Feature object. See the 'Feature' class for more
information.
'test_suite' -- the name of a test suite to run for the 'test' command. 'test_suite' -- the name of a test suite to run for the 'test' command.
If the user runs 'python setup.py test', the package will be installed, If the user runs 'python setup.py test', the package will be installed,
and the named test suite will be run. The format is the same as and the named test suite will be run. The format is the same as
...@@ -401,8 +384,7 @@ class Distribution(_Distribution): ...@@ -401,8 +384,7 @@ class Distribution(_Distribution):
for manipulating the distribution's contents. For example, the 'include()' for manipulating the distribution's contents. For example, the 'include()'
and 'exclude()' methods can be thought of as in-place add and subtract and 'exclude()' methods can be thought of as in-place add and subtract
commands that add or remove packages, modules, extensions, and so on from commands that add or remove packages, modules, extensions, and so on from
the distribution. They are used by the feature subsystem to configure the the distribution.
distribution for the included and excluded features.
""" """
_DISTUTILS_UNSUPPORTED_METADATA = { _DISTUTILS_UNSUPPORTED_METADATA = {
...@@ -432,10 +414,6 @@ class Distribution(_Distribution): ...@@ -432,10 +414,6 @@ class Distribution(_Distribution):
if not have_package_data: if not have_package_data:
self.package_data = {} self.package_data = {}
attrs = attrs or {} attrs = attrs or {}
if 'features' in attrs or 'require_features' in attrs:
Feature.warn_deprecated()
self.require_features = []
self.features = {}
self.dist_files = [] self.dist_files = []
# Filter-out setuptools' specific options. # Filter-out setuptools' specific options.
self.src_root = attrs.pop("src_root", None) self.src_root = attrs.pop("src_root", None)
...@@ -461,30 +439,40 @@ class Distribution(_Distribution): ...@@ -461,30 +439,40 @@ class Distribution(_Distribution):
value = default() if default else None value = default() if default else None
setattr(self.metadata, option, value) setattr(self.metadata, option, value)
if isinstance(self.metadata.version, numbers.Number): self.metadata.version = self._normalize_version(
self._validate_version(self.metadata.version))
self._finalize_requires()
@staticmethod
def _normalize_version(version):
if isinstance(version, setuptools.sic) or version is None:
return version
normalized = str(packaging.version.Version(version))
if version != normalized:
tmpl = "Normalizing '{version}' to '{normalized}'"
warnings.warn(tmpl.format(**locals()))
return normalized
return version
@staticmethod
def _validate_version(version):
if isinstance(version, numbers.Number):
# Some people apparently take "version number" too literally :) # Some people apparently take "version number" too literally :)
self.metadata.version = str(self.metadata.version) version = str(version)
if self.metadata.version is not None: if version is not None:
try: try:
ver = packaging.version.Version(self.metadata.version) packaging.version.Version(version)
normalized_version = str(ver)
if self.metadata.version != normalized_version:
warnings.warn(
"Normalizing '%s' to '%s'" % (
self.metadata.version,
normalized_version,
)
)
self.metadata.version = normalized_version
except (packaging.version.InvalidVersion, TypeError): except (packaging.version.InvalidVersion, TypeError):
warnings.warn( warnings.warn(
"The version specified (%r) is an invalid version, this " "The version specified (%r) is an invalid version, this "
"may not work as expected with newer versions of " "may not work as expected with newer versions of "
"setuptools, pip, and PyPI. Please see PEP 440 for more " "setuptools, pip, and PyPI. Please see PEP 440 for more "
"details." % self.metadata.version "details." % version
) )
self._finalize_requires() return setuptools.sic(version)
return version
def _finalize_requires(self): def _finalize_requires(self):
""" """
...@@ -702,17 +690,6 @@ class Distribution(_Distribution): ...@@ -702,17 +690,6 @@ class Distribution(_Distribution):
ignore_option_errors=ignore_option_errors) ignore_option_errors=ignore_option_errors)
self._finalize_requires() self._finalize_requires()
def parse_command_line(self):
"""Process features after parsing command line options"""
result = _Distribution.parse_command_line(self)
if self.features:
self._finalize_features()
return result
def _feature_attrname(self, name):
"""Convert feature name to corresponding option attribute name"""
return 'with_' + name.replace('-', '_')
def fetch_build_eggs(self, requires): def fetch_build_eggs(self, requires):
"""Resolve pre-setup requirements""" """Resolve pre-setup requirements"""
resolved_dists = pkg_resources.working_set.resolve( resolved_dists = pkg_resources.working_set.resolve(
...@@ -731,13 +708,13 @@ class Distribution(_Distribution): ...@@ -731,13 +708,13 @@ class Distribution(_Distribution):
to influence the order of execution. Smaller numbers to influence the order of execution. Smaller numbers
go first and the default is 0. go first and the default is 0.
""" """
hook_key = 'setuptools.finalize_distribution_options' group = 'setuptools.finalize_distribution_options'
def by_order(hook): def by_order(hook):
return getattr(hook, 'order', 0) return getattr(hook, 'order', 0)
eps = pkg_resources.iter_entry_points(hook_key) eps = map(lambda e: e.load(), pkg_resources.iter_entry_points(group))
for ep in sorted(eps, key=by_order): for ep in sorted(eps, key=by_order):
ep.load()(self) ep(self)
def _finalize_setup_keywords(self): def _finalize_setup_keywords(self):
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
...@@ -776,53 +753,6 @@ class Distribution(_Distribution): ...@@ -776,53 +753,6 @@ class Distribution(_Distribution):
from setuptools.installer import fetch_build_egg from setuptools.installer import fetch_build_egg
return fetch_build_egg(self, req) return fetch_build_egg(self, req)
def _finalize_feature_opts(self):
"""Add --with-X/--without-X options based on optional features"""
if not self.features:
return
go = []
no = self.negative_opt.copy()
for name, feature in self.features.items():
self._set_feature(name, None)
feature.validate(self)
if feature.optional:
descr = feature.description
incdef = ' (default)'
excdef = ''
if not feature.include_by_default():
excdef, incdef = incdef, excdef
new = (
('with-' + name, None, 'include ' + descr + incdef),
('without-' + name, None, 'exclude ' + descr + excdef),
)
go.extend(new)
no['without-' + name] = 'with-' + name
self.global_options = self.feature_options = go + self.global_options
self.negative_opt = self.feature_negopt = no
def _finalize_features(self):
"""Add/remove features and resolve dependencies between them"""
# First, flag all the enabled items (and thus their dependencies)
for name, feature in self.features.items():
enabled = self.feature_is_included(name)
if enabled or (enabled is None and feature.include_by_default()):
feature.include_in(self)
self._set_feature(name, 1)
# Then disable the rest, so that off-by-default features don't
# get flagged as errors when they're required by an enabled feature
for name, feature in self.features.items():
if not self.feature_is_included(name):
feature.exclude_from(self)
self._set_feature(name, 0)
def get_command_class(self, command): def get_command_class(self, command):
"""Pluggable version of get_command_class()""" """Pluggable version of get_command_class()"""
if command in self.cmdclass: if command in self.cmdclass:
...@@ -852,25 +782,6 @@ class Distribution(_Distribution): ...@@ -852,25 +782,6 @@ class Distribution(_Distribution):
self.cmdclass[ep.name] = cmdclass self.cmdclass[ep.name] = cmdclass
return _Distribution.get_command_list(self) return _Distribution.get_command_list(self)
def _set_feature(self, name, status):
"""Set feature's inclusion status"""
setattr(self, self._feature_attrname(name), status)
def feature_is_included(self, name):
"""Return 1 if feature is included, 0 if excluded, 'None' if unknown"""
return getattr(self, self._feature_attrname(name))
def include_feature(self, name):
"""Request inclusion of feature named 'name'"""
if self.feature_is_included(name) == 0:
descr = self.features[name].description
raise DistutilsOptionError(
descr + " is required, but was excluded or is not available"
)
self.features[name].include_in(self)
self._set_feature(name, 1)
def include(self, **attrs): def include(self, **attrs):
"""Add items to distribution that are named in keyword arguments """Add items to distribution that are named in keyword arguments
...@@ -1115,160 +1026,6 @@ class Distribution(_Distribution): ...@@ -1115,160 +1026,6 @@ class Distribution(_Distribution):
sys.stdout.detach(), encoding, errors, newline, line_buffering) sys.stdout.detach(), encoding, errors, newline, line_buffering)
class Feature:
"""
**deprecated** -- The `Feature` facility was never completely implemented
or supported, `has reported issues
<https://github.com/pypa/setuptools/issues/58>`_ and will be removed in
a future version.
A subset of the distribution that can be excluded if unneeded/wanted
Features are created using these keyword arguments:
'description' -- a short, human readable description of the feature, to
be used in error messages, and option help messages.
'standard' -- if true, the feature is included by default if it is
available on the current system. Otherwise, the feature is only
included if requested via a command line '--with-X' option, or if
another included feature requires it. The default setting is 'False'.
'available' -- if true, the feature is available for installation on the
current system. The default setting is 'True'.
'optional' -- if true, the feature's inclusion can be controlled from the
command line, using the '--with-X' or '--without-X' options. If
false, the feature's inclusion status is determined automatically,
based on 'availabile', 'standard', and whether any other feature
requires it. The default setting is 'True'.
'require_features' -- a string or sequence of strings naming features
that should also be included if this feature is included. Defaults to
empty list. May also contain 'Require' objects that should be
added/removed from the distribution.
'remove' -- a string or list of strings naming packages to be removed
from the distribution if this feature is *not* included. If the
feature *is* included, this argument is ignored. This argument exists
to support removing features that "crosscut" a distribution, such as
defining a 'tests' feature that removes all the 'tests' subpackages
provided by other features. The default for this argument is an empty
list. (Note: the named package(s) or modules must exist in the base
distribution when the 'setup()' function is initially called.)
other keywords -- any other keyword arguments are saved, and passed to
the distribution's 'include()' and 'exclude()' methods when the
feature is included or excluded, respectively. So, for example, you
could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be
added or removed from the distribution as appropriate.
A feature must include at least one 'requires', 'remove', or other
keyword argument. Otherwise, it can't affect the distribution in any way.
Note also that you can subclass 'Feature' to create your own specialized
feature types that modify the distribution in other ways when included or
excluded. See the docstrings for the various methods here for more detail.
Aside from the methods, the only feature attributes that distributions look
at are 'description' and 'optional'.
"""
@staticmethod
def warn_deprecated():
msg = (
"Features are deprecated and will be removed in a future "
"version. See https://github.com/pypa/setuptools/issues/65."
)
warnings.warn(msg, DistDeprecationWarning, stacklevel=3)
def __init__(
self, description, standard=False, available=True,
optional=True, require_features=(), remove=(), **extras):
self.warn_deprecated()
self.description = description
self.standard = standard
self.available = available
self.optional = optional
if isinstance(require_features, (str, Require)):
require_features = require_features,
self.require_features = [
r for r in require_features if isinstance(r, str)
]
er = [r for r in require_features if not isinstance(r, str)]
if er:
extras['require_features'] = er
if isinstance(remove, str):
remove = remove,
self.remove = remove
self.extras = extras
if not remove and not require_features and not extras:
raise DistutilsSetupError(
"Feature %s: must define 'require_features', 'remove', or "
"at least one of 'packages', 'py_modules', etc."
)
def include_by_default(self):
"""Should this feature be included by default?"""
return self.available and self.standard
def include_in(self, dist):
"""Ensure feature and its requirements are included in distribution
You may override this in a subclass to perform additional operations on
the distribution. Note that this method may be called more than once
per feature, and so should be idempotent.
"""
if not self.available:
raise DistutilsPlatformError(
self.description + " is required, "
"but is not available on this platform"
)
dist.include(**self.extras)
for f in self.require_features:
dist.include_feature(f)
def exclude_from(self, dist):
"""Ensure feature is excluded from distribution
You may override this in a subclass to perform additional operations on
the distribution. This method will be called at most once per
feature, and only after all included features have been asked to
include themselves.
"""
dist.exclude(**self.extras)
if self.remove:
for item in self.remove:
dist.exclude_package(item)
def validate(self, dist):
"""Verify that feature makes sense in context of distribution
This method is called by the distribution just before it parses its
command line. It checks to ensure that the 'remove' attribute, if any,
contains only valid package/module names that are present in the base
distribution when 'setup()' is called. You may override it in a
subclass to perform any other required validation of the feature
against a target distribution.
"""
for item in self.remove:
if not dist.has_contents_for(item):
raise DistutilsSetupError(
"%s wants to be able to remove %s, but the distribution"
" doesn't contain any packages or modules under %s"
% (self.description, item, item)
)
class DistDeprecationWarning(SetuptoolsDeprecationWarning): class DistDeprecationWarning(SetuptoolsDeprecationWarning):
"""Class for warning about deprecations in dist in """Class for warning about deprecations in dist in
setuptools. Not ignored by default, unlike DeprecationWarning.""" setuptools. Not ignored by default, unlike DeprecationWarning."""
...@@ -43,13 +43,6 @@ class VendorImporter: ...@@ -43,13 +43,6 @@ class VendorImporter:
__import__(extant) __import__(extant)
mod = sys.modules[extant] mod = sys.modules[extant]
sys.modules[fullname] = mod sys.modules[fullname] = mod
# mysterious hack:
# Remove the reference to the extant package/module
# on later Python versions to cause relative imports
# in the vendor package to resolve the same modules
# as those going through this importer.
if sys.version_info >= (3, ):
del sys.modules[extant]
return mod return mod
except ImportError: except ImportError:
pass pass
......
...@@ -64,8 +64,8 @@ def fetch_build_egg(dist, req): ...@@ -64,8 +64,8 @@ def fetch_build_egg(dist, req):
dist.announce( dist.announce(
'WARNING: The pip package is not available, falling back ' 'WARNING: The pip package is not available, falling back '
'to EasyInstall for handling setup_requires/test_requires; ' 'to EasyInstall for handling setup_requires/test_requires; '
'this is deprecated and will be removed in a future version.' 'this is deprecated and will be removed in a future version.',
, log.WARN log.WARN
) )
return _legacy_fetch_build_egg(dist, req) return _legacy_fetch_build_egg(dist, req)
# Warn if wheel is not. # Warn if wheel is not.
......
...@@ -7,11 +7,13 @@ Customized Mixin2to3 support: ...@@ -7,11 +7,13 @@ Customized Mixin2to3 support:
This module raises an ImportError on Python 2. This module raises an ImportError on Python 2.
""" """
import warnings
from distutils.util import Mixin2to3 as _Mixin2to3 from distutils.util import Mixin2to3 as _Mixin2to3
from distutils import log from distutils import log
from lib2to3.refactor import RefactoringTool, get_fixers_from_package from lib2to3.refactor import RefactoringTool, get_fixers_from_package
import setuptools import setuptools
from ._deprecation_warning import SetuptoolsDeprecationWarning
class DistutilsRefactoringTool(RefactoringTool): class DistutilsRefactoringTool(RefactoringTool):
...@@ -33,6 +35,13 @@ class Mixin2to3(_Mixin2to3): ...@@ -33,6 +35,13 @@ class Mixin2to3(_Mixin2to3):
return return
if not files: if not files:
return return
warnings.warn(
"2to3 support is deprecated. If the project still "
"requires Python 2 support, please migrate to "
"a single-codebase solution or employ an "
"independent conversion process.",
SetuptoolsDeprecationWarning)
log.info("Fixing " + " ".join(files)) log.info("Fixing " + " ".join(files))
self.__build_fixer_names() self.__build_fixer_names()
self.__exclude_fixers() self.__exclude_fixers()
......
...@@ -26,6 +26,7 @@ from os.path import join, isfile, isdir, dirname ...@@ -26,6 +26,7 @@ from os.path import join, isfile, isdir, dirname
import sys import sys
import platform import platform
import itertools import itertools
import subprocess
import distutils.errors import distutils.errors
from setuptools.extern.packaging.version import LegacyVersion from setuptools.extern.packaging.version import LegacyVersion
...@@ -142,6 +143,154 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): ...@@ -142,6 +143,154 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
raise raise
def _msvc14_find_vc2015():
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"Software\Microsoft\VisualStudio\SxS\VC7",
0,
winreg.KEY_READ | winreg.KEY_WOW64_32KEY
)
except OSError:
return None, None
best_version = 0
best_dir = None
with key:
for i in itertools.count():
try:
v, vc_dir, vt = winreg.EnumValue(key, i)
except OSError:
break
if v and vt == winreg.REG_SZ and isdir(vc_dir):
try:
version = int(float(v))
except (ValueError, TypeError):
continue
if version >= 14 and version > best_version:
best_version, best_dir = version, vc_dir
return best_version, best_dir
def _msvc14_find_vc2017():
"""Python 3.8 "distutils/_msvccompiler.py" backport
Returns "15, path" based on the result of invoking vswhere.exe
If no install is found, returns "None, None"
The version is returned to avoid unnecessarily changing the function
result. It may be ignored when the path is not None.
If vswhere.exe is not available, by definition, VS 2017 is not
installed.
"""
root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles")
if not root:
return None, None
try:
path = subprocess.check_output([
join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
"-latest",
"-prerelease",
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property", "installationPath",
"-products", "*",
]).decode(encoding="mbcs", errors="strict").strip()
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
return None, None
path = join(path, "VC", "Auxiliary", "Build")
if isdir(path):
return 15, path
return None, None
PLAT_SPEC_TO_RUNTIME = {
'x86': 'x86',
'x86_amd64': 'x64',
'x86_arm': 'arm',
'x86_arm64': 'arm64'
}
def _msvc14_find_vcvarsall(plat_spec):
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
_, best_dir = _msvc14_find_vc2017()
vcruntime = None
if plat_spec in PLAT_SPEC_TO_RUNTIME:
vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec]
else:
vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'
if best_dir:
vcredist = join(best_dir, "..", "..", "redist", "MSVC", "**",
vcruntime_plat, "Microsoft.VC14*.CRT",
"vcruntime140.dll")
try:
import glob
vcruntime = glob.glob(vcredist, recursive=True)[-1]
except (ImportError, OSError, LookupError):
vcruntime = None
if not best_dir:
best_version, best_dir = _msvc14_find_vc2015()
if best_version:
vcruntime = join(best_dir, 'redist', vcruntime_plat,
"Microsoft.VC140.CRT", "vcruntime140.dll")
if not best_dir:
return None, None
vcvarsall = join(best_dir, "vcvarsall.bat")
if not isfile(vcvarsall):
return None, None
if not vcruntime or not isfile(vcruntime):
vcruntime = None
return vcvarsall, vcruntime
def _msvc14_get_vc_env(plat_spec):
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
if "DISTUTILS_USE_SDK" in environ:
return {
key.lower(): value
for key, value in environ.items()
}
vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec)
if not vcvarsall:
raise distutils.errors.DistutilsPlatformError(
"Unable to find vcvarsall.bat"
)
try:
out = subprocess.check_output(
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
stderr=subprocess.STDOUT,
).decode('utf-16le', errors='replace')
except subprocess.CalledProcessError as exc:
raise distutils.errors.DistutilsPlatformError(
"Error executing {}".format(exc.cmd)
)
env = {
key.lower(): value
for key, _, value in
(line.partition('=') for line in out.splitlines())
if key and value
}
if vcruntime:
env['py_vcruntime_redist'] = vcruntime
return env
def msvc14_get_vc_env(plat_spec): def msvc14_get_vc_env(plat_spec):
""" """
Patched "distutils._msvccompiler._get_vc_env" for support extra Patched "distutils._msvccompiler._get_vc_env" for support extra
...@@ -159,16 +308,10 @@ def msvc14_get_vc_env(plat_spec): ...@@ -159,16 +308,10 @@ def msvc14_get_vc_env(plat_spec):
dict dict
environment environment
""" """
# Try to get environment from vcvarsall.bat (Classical way)
try:
return get_unpatched(msvc14_get_vc_env)(plat_spec)
except distutils.errors.DistutilsPlatformError:
# Pass error Vcvarsall.bat is missing
pass
# If error, try to set environment directly # Always use backport from CPython 3.8
try: try:
return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() return _msvc14_get_vc_env(plat_spec)
except distutils.errors.DistutilsPlatformError as exc: except distutils.errors.DistutilsPlatformError as exc:
_augment_exception(exc, 14.0) _augment_exception(exc, 14.0)
raise raise
...@@ -544,7 +687,7 @@ class SystemInfo: ...@@ -544,7 +687,7 @@ class SystemInfo:
# Except for VS15+, VC version is aligned with VS version # Except for VS15+, VC version is aligned with VS version
self.vs_ver = self.vc_ver = ( self.vs_ver = self.vc_ver = (
vc_ver or self._find_latest_available_vs_ver()) vc_ver or self._find_latest_available_vs_ver())
def _find_latest_available_vs_ver(self): def _find_latest_available_vs_ver(self):
""" """
...@@ -1225,7 +1368,7 @@ class EnvironmentInfo: ...@@ -1225,7 +1368,7 @@ class EnvironmentInfo:
arch_subdir = self.pi.target_dir(x64=True) arch_subdir = self.pi.target_dir(x64=True)
lib = join(self.si.WindowsSdkDir, 'lib') lib = join(self.si.WindowsSdkDir, 'lib')
libver = self._sdk_subdir libver = self._sdk_subdir
return [join(lib, '%sum%s' % (libver , arch_subdir))] return [join(lib, '%sum%s' % (libver, arch_subdir))]
@property @property
def OSIncludes(self): def OSIncludes(self):
...@@ -1274,13 +1417,16 @@ class EnvironmentInfo: ...@@ -1274,13 +1417,16 @@ class EnvironmentInfo:
libpath += [ libpath += [
ref, ref,
join(self.si.WindowsSdkDir, 'UnionMetadata'), join(self.si.WindowsSdkDir, 'UnionMetadata'),
join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), join(
ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
join(ref,'Windows.Networking.Connectivity.WwanContract', join(
'1.0.0.0'), ref, 'Windows.Networking.Connectivity.WwanContract',
join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', '1.0.0.0'),
'%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', join(
'neutral'), self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
'%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
'neutral'),
] ]
return libpath return libpath
......
...@@ -47,13 +47,17 @@ class Installer: ...@@ -47,13 +47,17 @@ class Installer:
"p = os.path.join(%(root)s, *%(pth)r)", "p = os.path.join(%(root)s, *%(pth)r)",
"importlib = has_mfs and __import__('importlib.util')", "importlib = has_mfs and __import__('importlib.util')",
"has_mfs and __import__('importlib.machinery')", "has_mfs and __import__('importlib.machinery')",
"m = has_mfs and " (
"m = has_mfs and "
"sys.modules.setdefault(%(pkg)r, " "sys.modules.setdefault(%(pkg)r, "
"importlib.util.module_from_spec(" "importlib.util.module_from_spec("
"importlib.machinery.PathFinder.find_spec(%(pkg)r, " "importlib.machinery.PathFinder.find_spec(%(pkg)r, "
"[os.path.dirname(p)])))", "[os.path.dirname(p)])))"
"m = m or " ),
"sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))", (
"m = m or "
"sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))"
),
"mp = (m or []) and m.__dict__.setdefault('__path__',[])", "mp = (m or []) and m.__dict__.setdefault('__path__',[])",
"(p not in mp) and mp.append(p)", "(p not in mp) and mp.append(p)",
) )
......
...@@ -46,7 +46,8 @@ __all__ = [ ...@@ -46,7 +46,8 @@ __all__ = [
_SOCKET_TIMEOUT = 15 _SOCKET_TIMEOUT = 15
_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" _tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools) user_agent = _tmpl.format(
py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools)
def parse_requirement_arg(spec): def parse_requirement_arg(spec):
...@@ -348,6 +349,8 @@ class PackageIndex(Environment): ...@@ -348,6 +349,8 @@ class PackageIndex(Environment):
f = self.open_url(url, tmpl % url) f = self.open_url(url, tmpl % url)
if f is None: if f is None:
return return
if isinstance(f, urllib.error.HTTPError) and f.code == 401:
self.info("Authentication error: %s" % f.msg)
self.fetched_urls[f.url] = True self.fetched_urls[f.url] = True
if 'html' not in f.headers.get('content-type', '').lower(): if 'html' not in f.headers.get('content-type', '').lower():
f.close() # not html, we can't process it f.close() # not html, we can't process it
...@@ -1050,7 +1053,7 @@ def open_with_auth(url, opener=urllib.request.urlopen): ...@@ -1050,7 +1053,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
parsed = urllib.parse.urlparse(url) parsed = urllib.parse.urlparse(url)
scheme, netloc, path, params, query, frag = parsed scheme, netloc, path, params, query, frag = parsed
# Double scheme does not raise on Mac OS X as revealed by a # Double scheme does not raise on macOS as revealed by a
# failing test. We would expect "nonnumeric port". Refs #20. # failing test. We would expect "nonnumeric port". Refs #20.
if netloc.endswith(':'): if netloc.endswith(':'):
raise http_client.InvalidURL("nonnumeric port: ''") raise http_client.InvalidURL("nonnumeric port: ''")
...@@ -1092,7 +1095,8 @@ def open_with_auth(url, opener=urllib.request.urlopen): ...@@ -1092,7 +1095,8 @@ def open_with_auth(url, opener=urllib.request.urlopen):
# copy of urllib.parse._splituser from Python 3.8 # copy of urllib.parse._splituser from Python 3.8
def _splituser(host): def _splituser(host):
"""splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'.""" """splituser('user[:passwd]@host[:port]')
--> 'user[:passwd]', 'host[:port]'."""
user, delim, host = host.rpartition('@') user, delim, host = host.rpartition('@')
return (user if delim else None), host return (user if delim else None), host
......
...@@ -16,7 +16,7 @@ def get_all_headers(message, key): ...@@ -16,7 +16,7 @@ def get_all_headers(message, key):
if six.PY2: if six.PY2:
def get_all_headers(message, key): def get_all_headers(message, key): # noqa
return message.getheaders(key) return message.getheaders(key)
......
...@@ -13,6 +13,8 @@ from setuptools.extern import six ...@@ -13,6 +13,8 @@ from setuptools.extern import six
from setuptools.extern.six.moves import builtins, map from setuptools.extern.six.moves import builtins, map
import pkg_resources.py31compat import pkg_resources.py31compat
from distutils.errors import DistutilsError
from pkg_resources import working_set
if sys.platform.startswith('java'): if sys.platform.startswith('java'):
import org.python.modules.posix.PosixModule as _os import org.python.modules.posix.PosixModule as _os
...@@ -23,8 +25,6 @@ try: ...@@ -23,8 +25,6 @@ try:
except NameError: except NameError:
_file = None _file = None
_open = open _open = open
from distutils.errors import DistutilsError
from pkg_resources import working_set
__all__ = [ __all__ = [
...@@ -374,7 +374,7 @@ class AbstractSandbox: ...@@ -374,7 +374,7 @@ class AbstractSandbox:
if hasattr(os, 'devnull'): if hasattr(os, 'devnull'):
_EXCEPTIONS = [os.devnull,] _EXCEPTIONS = [os.devnull]
else: else:
_EXCEPTIONS = [] _EXCEPTIONS = []
...@@ -466,7 +466,8 @@ class DirectorySandbox(AbstractSandbox): ...@@ -466,7 +466,8 @@ class DirectorySandbox(AbstractSandbox):
WRITE_FLAGS = functools.reduce( WRITE_FLAGS = functools.reduce(
operator.or_, [getattr(_os, a, 0) for a in operator.or_, [
getattr(_os, a, 0) for a in
"O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
) )
......
...@@ -38,22 +38,24 @@ def __boot(): ...@@ -38,22 +38,24 @@ def __boot():
else: else:
raise ImportError("Couldn't find the real 'site' module") raise ImportError("Couldn't find the real 'site' module")
known_paths = dict([(makepath(item)[1], 1) for item in sys.path]) # 2.2 comp # 2.2 comp
known_paths = dict([(
makepath(item)[1], 1) for item in sys.path]) # noqa
oldpos = getattr(sys, '__egginsert', 0) # save old insertion position oldpos = getattr(sys, '__egginsert', 0) # save old insertion position
sys.__egginsert = 0 # and reset the current one sys.__egginsert = 0 # and reset the current one
for item in PYTHONPATH: for item in PYTHONPATH:
addsitedir(item) addsitedir(item) # noqa
sys.__egginsert += oldpos # restore effective old position sys.__egginsert += oldpos # restore effective old position
d, nd = makepath(stdpath[0]) d, nd = makepath(stdpath[0]) # noqa
insert_at = None insert_at = None
new_path = [] new_path = []
for item in sys.path: for item in sys.path:
p, np = makepath(item) p, np = makepath(item) # noqa
if np == nd and insert_at is None: if np == nd and insert_at is None:
# We've hit the first 'system' path entry, so added entries go here # We've hit the first 'system' path entry, so added entries go here
......
...@@ -35,7 +35,8 @@ try: ...@@ -35,7 +35,8 @@ try:
except AttributeError: except AttributeError:
HTTPSHandler = HTTPSConnection = object HTTPSHandler = HTTPSConnection = object
is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) is_available = ssl is not None and object not in (
HTTPSHandler, HTTPSConnection)
try: try:
...@@ -85,8 +86,10 @@ if not match_hostname: ...@@ -85,8 +86,10 @@ if not match_hostname:
return dn.lower() == hostname.lower() return dn.lower() == hostname.lower()
# RFC 6125, section 6.4.3, subitem 1. # RFC 6125, section 6.4.3, subitem 1.
# The client SHOULD NOT attempt to match a presented identifier in which # The client SHOULD NOT attempt to match a
# the wildcard character comprises a label other than the left-most label. # presented identifier in which the wildcard
# character comprises a label other than the
# left-most label.
if leftmost == '*': if leftmost == '*':
# When '*' is a fragment by itself, it matches a non-empty dotless # When '*' is a fragment by itself, it matches a non-empty dotless
# fragment. # fragment.
...@@ -137,15 +140,16 @@ if not match_hostname: ...@@ -137,15 +140,16 @@ if not match_hostname:
return return
dnsnames.append(value) dnsnames.append(value)
if len(dnsnames) > 1: if len(dnsnames) > 1:
raise CertificateError("hostname %r " raise CertificateError(
"doesn't match either of %s" "hostname %r doesn't match either of %s"
% (hostname, ', '.join(map(repr, dnsnames)))) % (hostname, ', '.join(map(repr, dnsnames))))
elif len(dnsnames) == 1: elif len(dnsnames) == 1:
raise CertificateError("hostname %r " raise CertificateError(
"doesn't match %r" "hostname %r doesn't match %r"
% (hostname, dnsnames[0])) % (hostname, dnsnames[0]))
else: else:
raise CertificateError("no appropriate commonName or " raise CertificateError(
"no appropriate commonName or "
"subjectAltName fields were found") "subjectAltName fields were found")
...@@ -158,7 +162,8 @@ class VerifyingHTTPSHandler(HTTPSHandler): ...@@ -158,7 +162,8 @@ class VerifyingHTTPSHandler(HTTPSHandler):
def https_open(self, req): def https_open(self, req):
return self.do_open( return self.do_open(
lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw),
req
) )
......
...@@ -6,7 +6,7 @@ from setuptools.extern.six import PY2, PY3 ...@@ -6,7 +6,7 @@ from setuptools.extern.six import PY2, PY3
__all__ = [ __all__ = [
'fail_on_ascii', 'py2_only', 'py3_only' 'fail_on_ascii', 'py2_only', 'py3_only', 'ack_2to3'
] ]
...@@ -16,3 +16,5 @@ fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale") ...@@ -16,3 +16,5 @@ fail_on_ascii = pytest.mark.xfail(is_ascii, reason="Test fails in this locale")
py2_only = pytest.mark.skipif(not PY2, reason="Test runs on Python 2 only") py2_only = pytest.mark.skipif(not PY2, reason="Test runs on Python 2 only")
py3_only = pytest.mark.skipif(not PY3, reason="Test runs on Python 3 only") py3_only = pytest.mark.skipif(not PY3, reason="Test runs on Python 3 only")
ack_2to3 = pytest.mark.filterwarnings('ignore:2to3 support is deprecated')
mock mock
pytest-flake8 pytest-flake8
flake8-2020; python_version>="3.6"
virtualenv>=13.0.0 virtualenv>=13.0.0
pytest-virtualenv>=1.2.7 pytest-virtualenv>=1.2.7
pytest>=3.7 pytest>=3.7
......
"""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()
...@@ -7,6 +7,7 @@ import zipfile ...@@ -7,6 +7,7 @@ import zipfile
import pytest import pytest
from setuptools.dist import Distribution from setuptools.dist import Distribution
from setuptools import SetuptoolsDeprecationWarning
from . import contexts from . import contexts
...@@ -64,3 +65,17 @@ class Test: ...@@ -64,3 +65,17 @@ class Test:
names = list(zi.filename for zi in zip.filelist) names = list(zi.filename for zi in zip.filelist)
assert 'hi.pyc' in names assert 'hi.pyc' in names
assert 'hi.py' not 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()
...@@ -8,8 +8,7 @@ from setuptools.dist import Distribution ...@@ -8,8 +8,7 @@ from setuptools.dist import Distribution
class TestBuildCLib: class TestBuildCLib:
@mock.patch( @mock.patch(
'setuptools.command.build_clib.newer_pairwise_group' 'setuptools.command.build_clib.newer_pairwise_group')
)
def test_build_libraries(self, mock_newer): def test_build_libraries(self, mock_newer):
dist = Distribution() dist = Distribution()
cmd = build_clib(dist) cmd = build_clib(dist)
......
...@@ -23,6 +23,7 @@ class BuildBackendBase: ...@@ -23,6 +23,7 @@ class BuildBackendBase:
self.env = env self.env = env
self.backend_name = backend_name self.backend_name = backend_name
class BuildBackend(BuildBackendBase): class BuildBackend(BuildBackendBase):
"""PEP 517 Build Backend""" """PEP 517 Build Backend"""
...@@ -406,6 +407,28 @@ class TestBuildMetaBackend: ...@@ -406,6 +407,28 @@ class TestBuildMetaBackend:
assert expected == sorted(actual) assert expected == sorted(actual)
_sys_argv_0_passthrough = {
'setup.py': DALS("""
import os
import sys
__import__('setuptools').setup(
name='foo',
version='0.0.0',
)
sys_argv = os.path.abspath(sys.argv[0])
file_path = os.path.abspath('setup.py')
assert sys_argv == file_path
""")
}
def test_sys_argv_passthrough(self, tmpdir_cwd):
build_files(self._sys_argv_0_passthrough)
build_backend = self.get_build_backend()
with pytest.raises(AssertionError):
build_backend.build_sdist("temp")
class TestBuildMetaLegacyBackend(TestBuildMetaBackend): class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
backend_name = 'setuptools.build_meta:__legacy__' backend_name = 'setuptools.build_meta:__legacy__'
...@@ -417,3 +440,9 @@ class TestBuildMetaLegacyBackend(TestBuildMetaBackend): ...@@ -417,3 +440,9 @@ class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
build_backend = self.get_build_backend() build_backend = self.get_build_backend()
build_backend.build_sdist("temp") build_backend.build_sdist("temp")
def test_sys_argv_passthrough(self, tmpdir_cwd):
build_files(self._sys_argv_0_passthrough)
build_backend = self.get_build_backend()
build_backend.build_sdist("temp")
import os import os
import stat
import shutil
import pytest
from setuptools.dist import Distribution from setuptools.dist import Distribution
...@@ -20,3 +24,61 @@ def test_directories_in_package_data_glob(tmpdir_cwd): ...@@ -20,3 +24,61 @@ def test_directories_in_package_data_glob(tmpdir_cwd):
os.makedirs('path/subpath') os.makedirs('path/subpath')
dist.parse_command_line() dist.parse_command_line()
dist.run_commands() dist.run_commands()
def test_read_only(tmpdir_cwd):
"""
Ensure read-only flag is not preserved in copy
for package modules and package data, as that
causes problems with deleting read-only files on
Windows.
#1451
"""
dist = Distribution(dict(
script_name='setup.py',
script_args=['build_py'],
packages=['pkg'],
package_data={'pkg': ['data.dat']},
name='pkg',
))
os.makedirs('pkg')
open('pkg/__init__.py', 'w').close()
open('pkg/data.dat', 'w').close()
os.chmod('pkg/__init__.py', stat.S_IREAD)
os.chmod('pkg/data.dat', stat.S_IREAD)
dist.parse_command_line()
dist.run_commands()
shutil.rmtree('build')
@pytest.mark.xfail(
'platform.system() == "Windows"',
reason="On Windows, files do not have executable bits",
raises=AssertionError,
strict=True,
)
def test_executable_data(tmpdir_cwd):
"""
Ensure executable bit is preserved in copy for
package data, as users rely on it for scripts.
#2041
"""
dist = Distribution(dict(
script_name='setup.py',
script_args=['build_py'],
packages=['pkg'],
package_data={'pkg': ['run-me']},
name='pkg',
))
os.makedirs('pkg')
open('pkg/__init__.py', 'w').close()
open('pkg/run-me', 'w').close()
os.chmod('pkg/run-me', 0o700)
dist.parse_command_line()
dist.run_commands()
assert os.stat('build/lib/pkg/run-me').st_mode & stat.S_IEXEC, \
"Script is not executable"
...@@ -695,7 +695,7 @@ class TestOptions: ...@@ -695,7 +695,7 @@ class TestOptions:
) )
with get_dist(tmpdir) as dist: with get_dist(tmpdir) as dist:
assert set(dist.packages) == set( assert set(dist.packages) == set(
['fake_package', 'fake_package.sub_two']) ['fake_package', 'fake_package.sub_two'])
@py2_only @py2_only
def test_find_namespace_directive_fails_on_py2(self, tmpdir): def test_find_namespace_directive_fails_on_py2(self, tmpdir):
...@@ -748,7 +748,7 @@ class TestOptions: ...@@ -748,7 +748,7 @@ class TestOptions:
) )
with get_dist(tmpdir) as dist: with get_dist(tmpdir) as dist:
assert set(dist.packages) == { assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two' 'fake_package', 'fake_package.sub_two'
} }
def test_extras_require(self, tmpdir): def test_extras_require(self, tmpdir):
...@@ -881,7 +881,7 @@ class TestExternalSetters: ...@@ -881,7 +881,7 @@ class TestExternalSetters:
return None return None
@patch.object(_Distribution, '__init__', autospec=True) @patch.object(_Distribution, '__init__', autospec=True)
def test_external_setters(self, mock_parent_init, tmpdir): def test_external_setters(self, mock_parent_init, tmpdir):
mock_parent_init.side_effect = self._fake_distribution_init mock_parent_init.side_effect = self._fake_distribution_init
dist = Distribution(attrs={ dist = Distribution(attrs={
......
...@@ -17,6 +17,7 @@ import pytest ...@@ -17,6 +17,7 @@ import pytest
from setuptools.command.develop import develop from setuptools.command.develop import develop
from setuptools.dist import Distribution from setuptools.dist import Distribution
from setuptools.tests import ack_2to3
from . import contexts from . import contexts
from . import namespaces from . import namespaces
...@@ -65,6 +66,7 @@ class TestDevelop: ...@@ -65,6 +66,7 @@ class TestDevelop:
@pytest.mark.skipif( @pytest.mark.skipif(
in_virtualenv or in_venv, in_virtualenv or in_venv,
reason="Cannot run when invoked in a virtualenv or venv") reason="Cannot run when invoked in a virtualenv or venv")
@ack_2to3
def test_2to3_user_mode(self, test_env): def test_2to3_user_mode(self, test_env):
settings = dict( settings = dict(
name='foo', name='foo',
......
...@@ -5,12 +5,14 @@ from __future__ import unicode_literals ...@@ -5,12 +5,14 @@ from __future__ import unicode_literals
import io import io
import collections import collections
import re import re
import functools
from distutils.errors import DistutilsSetupError from distutils.errors import DistutilsSetupError
from setuptools.dist import ( from setuptools.dist import (
_get_unpatched, _get_unpatched,
check_package_data, check_package_data,
DistDeprecationWarning, DistDeprecationWarning,
) )
from setuptools import sic
from setuptools import Distribution from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin from setuptools.extern.six.moves.urllib_parse import urljoin
...@@ -61,7 +63,8 @@ def test_dist_fetch_build_egg(tmpdir): ...@@ -61,7 +63,8 @@ def test_dist_fetch_build_egg(tmpdir):
dist.fetch_build_egg(r) dist.fetch_build_egg(r)
for r in reqs for r in reqs
] ]
assert [dist.key for dist in resolved_dists if dist] == reqs # noqa below because on Python 2 it causes flakes
assert [dist.key for dist in resolved_dists if dist] == reqs # noqa
def test_dist__get_unpatched_deprecated(): def test_dist__get_unpatched_deprecated():
...@@ -69,75 +72,72 @@ def test_dist__get_unpatched_deprecated(): ...@@ -69,75 +72,72 @@ def test_dist__get_unpatched_deprecated():
def __read_test_cases(): def __read_test_cases():
# Metadata version 1.0 base = dict(
base_attrs = { name="package",
"name": "package", version="0.0.1",
"version": "0.0.1", author="Foo Bar",
"author": "Foo Bar", author_email="foo@bar.net",
"author_email": "foo@bar.net", long_description="Long\ndescription",
"long_description": "Long\ndescription", description="Short description",
"description": "Short description", keywords=["one", "two"],
"keywords": ["one", "two"] )
}
params = functools.partial(dict, base)
def merge_dicts(d1, d2):
d1 = d1.copy()
d1.update(d2)
return d1
test_cases = [ test_cases = [
('Metadata version 1.0', base_attrs.copy()), ('Metadata version 1.0', params()),
('Metadata version 1.1: Provides', merge_dicts(base_attrs, { ('Metadata version 1.1: Provides', params(
'provides': ['package'] provides=['package'],
})), )),
('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, { ('Metadata version 1.1: Obsoletes', params(
'obsoletes': ['foo'] obsoletes=['foo'],
})), )),
('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, { ('Metadata version 1.1: Classifiers', params(
'classifiers': [ classifiers=[
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
]})), ],
('Metadata version 1.1: Download URL', merge_dicts(base_attrs, { )),
'download_url': 'https://example.com' ('Metadata version 1.1: Download URL', params(
})), download_url='https://example.com',
('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, { )),
'python_requires': '>=3.7' ('Metadata Version 1.2: Requires-Python', params(
})), python_requires='>=3.7',
)),
pytest.param( pytest.param(
'Metadata Version 1.2: Project-Url', 'Metadata Version 1.2: Project-Url',
merge_dicts(base_attrs, { params(project_urls=dict(Foo='https://example.bar')),
'project_urls': { marks=pytest.mark.xfail(
'Foo': 'https://example.bar' reason="Issue #1578: project_urls not read",
} ),
}), marks=pytest.mark.xfail( ),
reason="Issue #1578: project_urls not read" ('Metadata Version 2.1: Long Description Content Type', params(
)), long_description_content_type='text/x-rst; charset=UTF-8',
('Metadata Version 2.1: Long Description Content Type', )),
merge_dicts(base_attrs, {
'long_description_content_type': 'text/x-rst; charset=UTF-8'
})),
pytest.param( pytest.param(
'Metadata Version 2.1: Provides Extra', 'Metadata Version 2.1: Provides Extra',
merge_dicts(base_attrs, { params(provides_extras=['foo', 'bar']),
'provides_extras': ['foo', 'bar'] marks=pytest.mark.xfail(reason="provides_extras not read"),
}), marks=pytest.mark.xfail(reason="provides_extras not read")), ),
('Missing author, missing author e-mail', ('Missing author', dict(
{'name': 'foo', 'version': '1.0.0'}), name='foo',
('Missing author', version='1.0.0',
{'name': 'foo', author_email='snorri@sturluson.name',
'version': '1.0.0', )),
'author_email': 'snorri@sturluson.name'}), ('Missing author e-mail', dict(
('Missing author e-mail', name='foo',
{'name': 'foo', version='1.0.0',
'version': '1.0.0', author='Snorri Sturluson',
'author': 'Snorri Sturluson'}), )),
('Missing author', ('Missing author and e-mail', dict(
{'name': 'foo', name='foo',
'version': '1.0.0', version='1.0.0',
'author': 'Snorri Sturluson'}), )),
('Bypass normalized version', dict(
name='foo',
version=sic('1.0.0a'),
)),
] ]
return test_cases return test_cases
...@@ -284,7 +284,7 @@ def test_provides_extras_deterministic_order(): ...@@ -284,7 +284,7 @@ def test_provides_extras_deterministic_order():
dist = Distribution(attrs) dist = Distribution(attrs)
assert dist.metadata.provides_extras == ['b', 'a'] assert dist.metadata.provides_extras == ['b', 'a']
CHECK_PACKAGE_DATA_TESTS = ( CHECK_PACKAGE_DATA_TESTS = (
# Valid. # Valid.
({ ({
...@@ -309,7 +309,8 @@ CHECK_PACKAGE_DATA_TESTS = ( ...@@ -309,7 +309,8 @@ CHECK_PACKAGE_DATA_TESTS = (
({ ({
'hello': str('*.msg'), 'hello': str('*.msg'),
}, ( }, (
"\"values of 'package_data' dict\" must be a list of strings (got '*.msg')" "\"values of 'package_data' dict\" "
"must be a list of strings (got '*.msg')"
)), )),
# Invalid value type (generators are single use) # Invalid value type (generators are single use)
({ ({
...@@ -321,10 +322,12 @@ CHECK_PACKAGE_DATA_TESTS = ( ...@@ -321,10 +322,12 @@ CHECK_PACKAGE_DATA_TESTS = (
) )
@pytest.mark.parametrize('package_data, expected_message', CHECK_PACKAGE_DATA_TESTS) @pytest.mark.parametrize(
'package_data, expected_message', CHECK_PACKAGE_DATA_TESTS)
def test_check_package_data(package_data, expected_message): def test_check_package_data(package_data, expected_message):
if expected_message is None: if expected_message is None:
assert check_package_data(None, 'package_data', package_data) is None assert check_package_data(None, 'package_data', package_data) is None
else: else:
with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)): with pytest.raises(
DistutilsSetupError, match=re.escape(expected_message)):
check_package_data(None, str('package_data'), package_data) check_package_data(None, str('package_data'), package_data)
...@@ -629,7 +629,7 @@ class TestSetupRequires: ...@@ -629,7 +629,7 @@ class TestSetupRequires:
test_pkg = create_setup_requires_package( test_pkg = create_setup_requires_package(
temp_dir, setup_attrs=dict(version='attr: foobar.version'), temp_dir, setup_attrs=dict(version='attr: foobar.version'),
make_package=make_dependency_sdist, make_package=make_dependency_sdist,
use_setup_cfg=use_setup_cfg+('version',), use_setup_cfg=use_setup_cfg + ('version',),
) )
test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_py = os.path.join(test_pkg, 'setup.py')
with contexts.quiet() as (stdout, stderr): with contexts.quiet() as (stdout, stderr):
...@@ -671,8 +671,10 @@ class TestSetupRequires: ...@@ -671,8 +671,10 @@ class TestSetupRequires:
dep_url = path_to_url(dep_sdist, authority='localhost') dep_url = path_to_url(dep_sdist, authority='localhost')
test_pkg = create_setup_requires_package( test_pkg = create_setup_requires_package(
temp_dir, temp_dir,
'python-xlib', '0.19', # Ignored (overriden by setup_attrs). # Ignored (overriden by setup_attrs)
setup_attrs=dict(setup_requires='dependency @ %s' % dep_url)) 'python-xlib', '0.19',
setup_attrs=dict(
setup_requires='dependency @ %s' % dep_url))
test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_py = os.path.join(test_pkg, 'setup.py')
run_setup(test_setup_py, [str('--version')]) run_setup(test_setup_py, [str('--version')])
assert len(mock_index.requests) == 0 assert len(mock_index.requests) == 0
...@@ -710,11 +712,14 @@ class TestSetupRequires: ...@@ -710,11 +712,14 @@ class TestSetupRequires:
dep_1_0_sdist = 'dep-1.0.tar.gz' dep_1_0_sdist = 'dep-1.0.tar.gz'
dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist)) dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist))
dep_1_0_python_requires = '>=2.7' dep_1_0_python_requires = '>=2.7'
make_python_requires_sdist(str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) make_python_requires_sdist(
str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires)
dep_2_0_sdist = 'dep-2.0.tar.gz' dep_2_0_sdist = 'dep-2.0.tar.gz'
dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist)) dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist))
dep_2_0_python_requires = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*' dep_2_0_python_requires = '!=' + '.'.join(
make_python_requires_sdist(str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) map(str, sys.version_info[:2])) + '.*'
make_python_requires_sdist(
str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires)
index = tmpdir / 'index.html' index = tmpdir / 'index.html'
index.write_text(DALS( index.write_text(DALS(
''' '''
...@@ -726,35 +731,41 @@ class TestSetupRequires: ...@@ -726,35 +731,41 @@ class TestSetupRequires:
<a href="{dep_2_0_url}" data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/> <a href="{dep_2_0_url}" data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/>
</body> </body>
</html> </html>
''').format( ''').format( # noqa
dep_1_0_url=dep_1_0_url, dep_1_0_url=dep_1_0_url,
dep_1_0_sdist=dep_1_0_sdist, dep_1_0_sdist=dep_1_0_sdist,
dep_1_0_python_requires=dep_1_0_python_requires, dep_1_0_python_requires=dep_1_0_python_requires,
dep_2_0_url=dep_2_0_url, dep_2_0_url=dep_2_0_url,
dep_2_0_sdist=dep_2_0_sdist, dep_2_0_sdist=dep_2_0_sdist,
dep_2_0_python_requires=dep_2_0_python_requires, dep_2_0_python_requires=dep_2_0_python_requires,
), 'utf-8') ), 'utf-8')
index_url = path_to_url(str(index)) index_url = path_to_url(str(index))
with contexts.save_pkg_resources_state(): with contexts.save_pkg_resources_state():
test_pkg = create_setup_requires_package( test_pkg = create_setup_requires_package(
str(tmpdir), str(tmpdir),
'python-xlib', '0.19', # Ignored (overriden by setup_attrs). 'python-xlib', '0.19', # Ignored (overriden by setup_attrs).
setup_attrs=dict(setup_requires='dep', dependency_links=[index_url])) setup_attrs=dict(
setup_requires='dep', dependency_links=[index_url]))
test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_py = os.path.join(test_pkg, 'setup.py')
run_setup(test_setup_py, [str('--version')]) run_setup(test_setup_py, [str('--version')])
eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) eggs = list(map(str, pkg_resources.find_distributions(
os.path.join(test_pkg, '.eggs'))))
assert eggs == ['dep 1.0'] assert eggs == ['dep 1.0']
@pytest.mark.parametrize('use_legacy_installer,with_dependency_links_in_setup_py', @pytest.mark.parametrize(
itertools.product((False, True), (False, True))) 'use_legacy_installer,with_dependency_links_in_setup_py',
def test_setup_requires_with_find_links_in_setup_cfg(self, monkeypatch, itertools.product((False, True), (False, True)))
use_legacy_installer, def test_setup_requires_with_find_links_in_setup_cfg(
with_dependency_links_in_setup_py): self, monkeypatch, use_legacy_installer,
with_dependency_links_in_setup_py):
monkeypatch.setenv(str('PIP_RETRIES'), str('0')) monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
with contexts.save_pkg_resources_state(): with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir: with contexts.tempdir() as temp_dir:
make_trivial_sdist(os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42') make_trivial_sdist(
os.path.join(temp_dir, 'python-xlib-42.tar.gz'),
'python-xlib',
'42')
test_pkg = os.path.join(temp_dir, 'test_pkg') test_pkg = os.path.join(temp_dir, 'test_pkg')
test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_py = os.path.join(test_pkg, 'setup.py')
test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
...@@ -771,7 +782,7 @@ class TestSetupRequires: ...@@ -771,7 +782,7 @@ class TestSetupRequires:
installer.fetch_build_egg = installer._legacy_fetch_build_egg installer.fetch_build_egg = installer._legacy_fetch_build_egg
setup(setup_requires='python-xlib==42', setup(setup_requires='python-xlib==42',
dependency_links={dependency_links!r}) dependency_links={dependency_links!r})
''').format(use_legacy_installer=use_legacy_installer, ''').format(use_legacy_installer=use_legacy_installer, # noqa
dependency_links=dependency_links)) dependency_links=dependency_links))
with open(test_setup_cfg, 'w') as fp: with open(test_setup_cfg, 'w') as fp:
fp.write(DALS( fp.write(DALS(
...@@ -783,14 +794,17 @@ class TestSetupRequires: ...@@ -783,14 +794,17 @@ class TestSetupRequires:
find_links=temp_dir)) find_links=temp_dir))
run_setup(test_setup_py, [str('--version')]) run_setup(test_setup_py, [str('--version')])
def test_setup_requires_with_transitive_extra_dependency(self, monkeypatch): def test_setup_requires_with_transitive_extra_dependency(
self, monkeypatch):
# Use case: installing a package with a build dependency on # Use case: installing a package with a build dependency on
# an already installed `dep[extra]`, which in turn depends # an already installed `dep[extra]`, which in turn depends
# on `extra_dep` (whose is not already installed). # on `extra_dep` (whose is not already installed).
with contexts.save_pkg_resources_state(): with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir: with contexts.tempdir() as temp_dir:
# Create source distribution for `extra_dep`. # Create source distribution for `extra_dep`.
make_trivial_sdist(os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0') make_trivial_sdist(
os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'),
'extra_dep', '1.0')
# Create source tree for `dep`. # Create source tree for `dep`.
dep_pkg = os.path.join(temp_dir, 'dep') dep_pkg = os.path.join(temp_dir, 'dep')
os.mkdir(dep_pkg) os.mkdir(dep_pkg)
...@@ -806,12 +820,12 @@ class TestSetupRequires: ...@@ -806,12 +820,12 @@ class TestSetupRequires:
'setup.cfg': '', 'setup.cfg': '',
}, prefix=dep_pkg) }, prefix=dep_pkg)
# "Install" dep. # "Install" dep.
run_setup(os.path.join(dep_pkg, 'setup.py'), [str('dist_info')]) run_setup(
os.path.join(dep_pkg, 'setup.py'), [str('dist_info')])
working_set.add_entry(dep_pkg) working_set.add_entry(dep_pkg)
# Create source tree for test package. # Create source tree for test package.
test_pkg = os.path.join(temp_dir, 'test_pkg') test_pkg = os.path.join(temp_dir, 'test_pkg')
test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_py = os.path.join(test_pkg, 'setup.py')
test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
os.mkdir(test_pkg) os.mkdir(test_pkg)
with open(test_setup_py, 'w') as fp: with open(test_setup_py, 'w') as fp:
fp.write(DALS( fp.write(DALS(
...@@ -881,16 +895,19 @@ def make_nspkg_sdist(dist_path, distname, version): ...@@ -881,16 +895,19 @@ def make_nspkg_sdist(dist_path, distname, version):
def make_python_requires_sdist(dist_path, distname, version, python_requires): def make_python_requires_sdist(dist_path, distname, version, python_requires):
make_sdist(dist_path, [ make_sdist(dist_path, [
('setup.py', DALS("""\ (
import setuptools 'setup.py',
setuptools.setup( DALS("""\
name={name!r}, import setuptools
version={version!r}, setuptools.setup(
python_requires={python_requires!r}, name={name!r},
) version={version!r},
""").format(name=distname, version=version, python_requires={python_requires!r},
python_requires=python_requires)), )
('setup.cfg', ''), """).format(
name=distname, version=version,
python_requires=python_requires)),
('setup.cfg', ''),
]) ])
...@@ -948,16 +965,16 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', ...@@ -948,16 +965,16 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
value = ';'.join(value) value = ';'.join(value)
section.append('%s: %s' % (name, value)) section.append('%s: %s' % (name, value))
test_setup_cfg_contents = DALS( test_setup_cfg_contents = DALS(
""" """
[metadata] [metadata]
{metadata} {metadata}
[options] [options]
{options} {options}
""" """
).format( ).format(
options='\n'.join(options), options='\n'.join(options),
metadata='\n'.join(metadata), metadata='\n'.join(metadata),
) )
else: else:
test_setup_cfg_contents = '' test_setup_cfg_contents = ''
with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f: with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f:
......
...@@ -525,19 +525,19 @@ class TestEggInfo: ...@@ -525,19 +525,19 @@ class TestEggInfo:
license_file = LICENSE license_file = LICENSE
"""), """),
'LICENSE': "Test license" 'LICENSE': "Test license"
}, True), # with license }, True), # with license
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
license_file = INVALID_LICENSE license_file = INVALID_LICENSE
"""), """),
'LICENSE': "Test license" 'LICENSE': "Test license"
}, False), # with an invalid license }, False), # with an invalid license
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
"""), """),
'LICENSE': "Test license" 'LICENSE': "Test license"
}, False), # no license_file attribute }, False), # no license_file attribute
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -545,7 +545,7 @@ class TestEggInfo: ...@@ -545,7 +545,7 @@ class TestEggInfo:
"""), """),
'MANIFEST.in': "exclude LICENSE", 'MANIFEST.in': "exclude LICENSE",
'LICENSE': "Test license" 'LICENSE': "Test license"
}, False) # license file is manually excluded }, False) # license file is manually excluded
]) ])
def test_setup_cfg_license_file( def test_setup_cfg_license_file(
self, tmpdir_cwd, env, files, license_in_sources): self, tmpdir_cwd, env, files, license_in_sources):
...@@ -565,7 +565,8 @@ class TestEggInfo: ...@@ -565,7 +565,8 @@ class TestEggInfo:
assert 'LICENSE' in sources_text assert 'LICENSE' in sources_text
else: else:
assert 'LICENSE' not in sources_text assert 'LICENSE' not in sources_text
assert 'INVALID_LICENSE' not in sources_text # for invalid license test # for invalid license test
assert 'INVALID_LICENSE' not in sources_text
@pytest.mark.parametrize("files, incl_licenses, excl_licenses", [ @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [
({ ({
...@@ -577,7 +578,7 @@ class TestEggInfo: ...@@ -577,7 +578,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -585,7 +586,7 @@ class TestEggInfo: ...@@ -585,7 +586,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -594,7 +595,7 @@ class TestEggInfo: ...@@ -594,7 +595,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -602,7 +603,7 @@ class TestEggInfo: ...@@ -602,7 +603,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -610,7 +611,7 @@ class TestEggInfo: ...@@ -610,7 +611,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -619,12 +620,12 @@ class TestEggInfo: ...@@ -619,12 +620,12 @@ class TestEggInfo:
INVALID_LICENSE INVALID_LICENSE
"""), """),
'LICENSE-ABC': "Test license" 'LICENSE-ABC': "Test license"
}, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
"""), """),
'LICENSE': "Test license" 'LICENSE': "Test license"
}, [], ['LICENSE']), # no license_files attribute }, [], ['LICENSE']), # no license_files attribute
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -632,7 +633,7 @@ class TestEggInfo: ...@@ -632,7 +633,7 @@ class TestEggInfo:
"""), """),
'MANIFEST.in': "exclude LICENSE", 'MANIFEST.in': "exclude LICENSE",
'LICENSE': "Test license" 'LICENSE': "Test license"
}, [], ['LICENSE']), # license file is manually excluded }, [], ['LICENSE']), # license file is manually excluded
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -643,7 +644,7 @@ class TestEggInfo: ...@@ -643,7 +644,7 @@ class TestEggInfo:
'MANIFEST.in': "exclude LICENSE-XYZ", 'MANIFEST.in': "exclude LICENSE-XYZ",
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded
]) ])
def test_setup_cfg_license_files( def test_setup_cfg_license_files(
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
...@@ -674,7 +675,7 @@ class TestEggInfo: ...@@ -674,7 +675,7 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -684,7 +685,8 @@ class TestEggInfo: ...@@ -684,7 +685,8 @@ class TestEggInfo:
"""), """),
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # license_file is still singular # license_file is still singular
}, [], ['LICENSE-ABC', 'LICENSE-XYZ']),
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -696,7 +698,7 @@ class TestEggInfo: ...@@ -696,7 +698,7 @@ class TestEggInfo:
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-PQR': "PQR license", 'LICENSE-PQR': "PQR license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -709,7 +711,8 @@ class TestEggInfo: ...@@ -709,7 +711,8 @@ class TestEggInfo:
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-PQR': "PQR license", 'LICENSE-PQR': "PQR license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license # duplicate license
}, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []),
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -720,7 +723,8 @@ class TestEggInfo: ...@@ -720,7 +723,8 @@ class TestEggInfo:
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-PQR': "PQR license", 'LICENSE-PQR': "PQR license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset # combined subset
}, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']),
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -730,7 +734,8 @@ class TestEggInfo: ...@@ -730,7 +734,8 @@ class TestEggInfo:
LICENSE-PQR LICENSE-PQR
"""), """),
'LICENSE-PQR': "Test license" 'LICENSE-PQR': "Test license"
}, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']), # with invalid licenses # with invalid licenses
}, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']),
({ ({
'setup.cfg': DALS(""" 'setup.cfg': DALS("""
[metadata] [metadata]
...@@ -743,7 +748,8 @@ class TestEggInfo: ...@@ -743,7 +748,8 @@ class TestEggInfo:
'LICENSE-ABC': "ABC license", 'LICENSE-ABC': "ABC license",
'LICENSE-PQR': "PQR license", 'LICENSE-PQR': "PQR license",
'LICENSE-XYZ': "XYZ license" 'LICENSE-XYZ': "XYZ license"
}, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']) # manually excluded # manually excluded
}, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR'])
]) ])
def test_setup_cfg_license_file_license_files( def test_setup_cfg_license_file_license_files(
self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
......
import importlib
import pickle
from setuptools import Distribution
from setuptools.extern import ordered_set
from setuptools.tests import py3_only
def test_reimport_extern():
ordered_set2 = importlib.import_module(ordered_set.__name__)
assert ordered_set is ordered_set2
def test_orderedset_pickle_roundtrip():
o1 = ordered_set.OrderedSet([1, 2, 5])
o2 = pickle.loads(pickle.dumps(o1))
assert o1 == o2
@py3_only
def test_distribution_picklable():
pickle.loads(pickle.dumps(Distribution()))
# -*- coding: utf-8 -*-
"""
Tests for msvc support module (msvc14 unit tests).
"""
import os
from distutils.errors import DistutilsPlatformError
import pytest
import sys
@pytest.mark.skipif(sys.platform != "win32",
reason="These tests are only for win32")
class TestMSVC14:
"""Python 3.8 "distutils/tests/test_msvccompiler.py" backport"""
def test_no_compiler(self):
import setuptools.msvc as _msvccompiler
# makes sure query_vcvarsall raises
# a DistutilsPlatformError if the compiler
# is not found
def _find_vcvarsall(plat_spec):
return None, None
old_find_vcvarsall = _msvccompiler._msvc14_find_vcvarsall
_msvccompiler._msvc14_find_vcvarsall = _find_vcvarsall
try:
pytest.raises(DistutilsPlatformError,
_msvccompiler._msvc14_get_vc_env,
'wont find this version')
finally:
_msvccompiler._msvc14_find_vcvarsall = old_find_vcvarsall
@pytest.mark.skipif(sys.version_info[0] < 3,
reason="Unicode requires encode/decode on Python 2")
def test_get_vc_env_unicode(self):
import setuptools.msvc as _msvccompiler
test_var = 'ṰḖṤṪ┅ṼẨṜ'
test_value = '₃⁴₅'
# Ensure we don't early exit from _get_vc_env
old_distutils_use_sdk = os.environ.pop('DISTUTILS_USE_SDK', None)
os.environ[test_var] = test_value
try:
env = _msvccompiler._msvc14_get_vc_env('x86')
assert test_var.lower() in env
assert test_value == env[test_var.lower()]
finally:
os.environ.pop(test_var)
if old_distutils_use_sdk:
os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk
def test_get_vc2017(self):
import setuptools.msvc as _msvccompiler
# This function cannot be mocked, so pass it if we find VS 2017
# and mark it skipped if we do not.
version, path = _msvccompiler._msvc14_find_vc2017()
if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') in [
'Visual Studio 2017'
]:
assert version
if version:
assert version >= 15
assert os.path.isdir(path)
else:
pytest.skip("VS 2017 is not installed")
def test_get_vc2015(self):
import setuptools.msvc as _msvccompiler
# This function cannot be mocked, so pass it if we find VS 2015
# and mark it skipped if we do not.
version, path = _msvccompiler._msvc14_find_vc2015()
if os.environ.get('APPVEYOR_BUILD_WORKER_IMAGE', '') in [
'Visual Studio 2015', 'Visual Studio 2017'
]:
assert version
if version:
assert version >= 14
assert os.path.isdir(path)
else:
pytest.skip("VS 2015 is not installed")
...@@ -3,15 +3,14 @@ from __future__ import absolute_import ...@@ -3,15 +3,14 @@ from __future__ import absolute_import
import sys import sys
import os import os
import distutils.errors import distutils.errors
import platform
from setuptools.extern import six from setuptools.extern import six
from setuptools.extern.six.moves import urllib, http_client from setuptools.extern.six.moves import urllib, http_client
import mock import mock
import pytest import pytest
import pkg_resources
import setuptools.package_index import setuptools.package_index
from setuptools.tests.server import IndexServer
from .textwrap import DALS from .textwrap import DALS
...@@ -114,43 +113,6 @@ class TestPackageIndex: ...@@ -114,43 +113,6 @@ class TestPackageIndex:
url = 'file:///tmp/test_package_index' url = 'file:///tmp/test_package_index'
assert index.url_ok(url, True) assert index.url_ok(url, True)
def test_links_priority(self):
"""
Download links from the pypi simple index should be used before
external download links.
https://bitbucket.org/tarek/distribute/issue/163
Usecase :
- someone uploads a package on pypi, a md5 is generated
- someone manually copies this link (with the md5 in the url) onto an
external page accessible from the package page.
- someone reuploads the package (with a different md5)
- while easy_installing, an MD5 error occurs because the external link
is used
-> Setuptools should use the link from pypi, not the external one.
"""
if sys.platform.startswith('java'):
# Skip this test on jython because binding to :0 fails
return
# start an index server
server = IndexServer()
server.start()
index_url = server.base_url() + 'test_links_priority/simple/'
# scan a test index
pi = setuptools.package_index.PackageIndex(index_url)
requirement = pkg_resources.Requirement.parse('foobar')
pi.find_packages(requirement)
server.stop()
# the distribution has been found
assert 'foobar' in pi
# we have only one link, because links are compared without md5
assert len(pi['foobar']) == 1
# the link should be from the index
assert 'correct_md5' in pi['foobar'][0].location
def test_parse_bdist_wininst(self): def test_parse_bdist_wininst(self):
parse = setuptools.package_index.parse_bdist_wininst parse = setuptools.package_index.parse_bdist_wininst
...@@ -221,11 +183,11 @@ class TestPackageIndex: ...@@ -221,11 +183,11 @@ class TestPackageIndex:
('+ubuntu_0', '+ubuntu.0'), ('+ubuntu_0', '+ubuntu.0'),
] ]
versions = [ versions = [
[''.join([e, r, p, l]) for l in ll] [''.join([e, r, p, loc]) for loc in locs]
for e in epoch for e in epoch
for r in releases for r in releases
for p in sum([pre, post, dev], ['']) for p in sum([pre, post, dev], [''])
for ll in local] for locs in local]
for v, vc in versions: for v, vc in versions:
dists = list(setuptools.package_index.distros_for_url( dists = list(setuptools.package_index.distros_for_url(
'http://example.com/example.zip#egg=example-' + v)) 'http://example.com/example.zip#egg=example-' + v))
...@@ -322,17 +284,27 @@ class TestContentCheckers: ...@@ -322,17 +284,27 @@ class TestContentCheckers:
assert rep == 'My message about md5' assert rep == 'My message about md5'
@pytest.fixture
def temp_home(tmpdir, monkeypatch):
key = (
'USERPROFILE'
if platform.system() == 'Windows' and sys.version_info > (3, 8) else
'HOME'
)
monkeypatch.setitem(os.environ, key, str(tmpdir))
return tmpdir
class TestPyPIConfig: class TestPyPIConfig:
def test_percent_in_password(self, tmpdir, monkeypatch): def test_percent_in_password(self, temp_home):
monkeypatch.setitem(os.environ, 'HOME', str(tmpdir)) pypirc = temp_home / '.pypirc'
pypirc = tmpdir / '.pypirc' pypirc.write(DALS("""
with pypirc.open('w') as strm: [pypi]
strm.write(DALS(""" repository=https://pypi.org
[pypi] username=jaraco
repository=https://pypi.org password=pity%
username=jaraco """))
password=pity%
"""))
cfg = setuptools.package_index.PyPIConfig() cfg = setuptools.package_index.PyPIConfig()
cred = cfg.creds_by_repository['https://pypi.org'] cred = cfg.creds_by_repository['https://pypi.org']
assert cred.username == 'jaraco' assert cred.username == 'jaraco'
......
...@@ -4,7 +4,7 @@ import sys ...@@ -4,7 +4,7 @@ import sys
import os import os
import distutils.core import distutils.core
import distutils.cmd import distutils.cmd
from distutils.errors import DistutilsOptionError, DistutilsPlatformError from distutils.errors import DistutilsOptionError
from distutils.errors import DistutilsSetupError from distutils.errors import DistutilsSetupError
from distutils.core import Extension from distutils.core import Extension
from distutils.version import LooseVersion from distutils.version import LooseVersion
...@@ -14,7 +14,6 @@ import pytest ...@@ -14,7 +14,6 @@ import pytest
import setuptools import setuptools
import setuptools.dist import setuptools.dist
import setuptools.depends as dep import setuptools.depends as dep
from setuptools import Feature
from setuptools.depends import Require from setuptools.depends import Require
from setuptools.extern import six from setuptools.extern import six
...@@ -108,6 +107,11 @@ class TestDepends: ...@@ -108,6 +107,11 @@ class TestDepends:
assert not req.is_present() assert not req.is_present()
assert not req.is_current() assert not req.is_current()
@needs_bytecode
def test_require_present(self):
# In #1896, this test was failing for months with the only
# complaint coming from test runners (not end users).
# TODO: Evaluate if this code is needed at all.
req = Require('Tests', None, 'tests', homepage="http://example.com") req = Require('Tests', None, 'tests', homepage="http://example.com")
assert req.format is None assert req.format is None
assert req.attribute is None assert req.attribute is None
...@@ -211,86 +215,6 @@ class TestDistro: ...@@ -211,86 +215,6 @@ class TestDistro:
self.dist.exclude(package_dir=['q']) self.dist.exclude(package_dir=['q'])
@pytest.mark.filterwarnings('ignore:Features are deprecated')
class TestFeatures:
def setup_method(self, method):
self.req = Require('Distutils', '1.0.3', 'distutils')
self.dist = makeSetup(
features={
'foo': Feature(
"foo", standard=True, require_features=['baz', self.req]),
'bar': Feature("bar", standard=True, packages=['pkg.bar'],
py_modules=['bar_et'], remove=['bar.ext'],
),
'baz': Feature(
"baz", optional=False, packages=['pkg.baz'],
scripts=['scripts/baz_it'],
libraries=[('libfoo', 'foo/foofoo.c')]
),
'dwim': Feature("DWIM", available=False, remove='bazish'),
},
script_args=['--without-bar', 'install'],
packages=['pkg.bar', 'pkg.foo'],
py_modules=['bar_et', 'bazish'],
ext_modules=[Extension('bar.ext', ['bar.c'])]
)
def testDefaults(self):
assert not Feature(
"test", standard=True, remove='x', available=False
).include_by_default()
assert Feature("test", standard=True, remove='x').include_by_default()
# Feature must have either kwargs, removes, or require_features
with pytest.raises(DistutilsSetupError):
Feature("test")
def testAvailability(self):
with pytest.raises(DistutilsPlatformError):
self.dist.features['dwim'].include_in(self.dist)
def testFeatureOptions(self):
dist = self.dist
assert (
('with-dwim', None, 'include DWIM') in dist.feature_options
)
assert (
('without-dwim', None, 'exclude DWIM (default)')
in dist.feature_options
)
assert (
('with-bar', None, 'include bar (default)') in dist.feature_options
)
assert (
('without-bar', None, 'exclude bar') in dist.feature_options
)
assert dist.feature_negopt['without-foo'] == 'with-foo'
assert dist.feature_negopt['without-bar'] == 'with-bar'
assert dist.feature_negopt['without-dwim'] == 'with-dwim'
assert ('without-baz' not in dist.feature_negopt)
def testUseFeatures(self):
dist = self.dist
assert dist.with_foo == 1
assert dist.with_bar == 0
assert dist.with_baz == 1
assert ('bar_et' not in dist.py_modules)
assert ('pkg.bar' not in dist.packages)
assert ('pkg.baz' in dist.packages)
assert ('scripts/baz_it' in dist.scripts)
assert (('libfoo', 'foo/foofoo.c') in dist.libraries)
assert dist.ext_modules == []
assert dist.require_features == [self.req]
# If we ask for bar, it should fail because we explicitly disabled
# it on the command line
with pytest.raises(DistutilsOptionError):
dist.include_feature('bar')
def testFeatureWithInvalidRemove(self):
with pytest.raises(SystemExit):
makeSetup(features={'x': Feature('x', remove='y')})
class TestCommandTests: class TestCommandTests:
def testTestIsCommand(self): def testTestIsCommand(self):
test_cmd = makeSetup().get_command_obj('test') test_cmd = makeSetup().get_command_obj('test')
......
...@@ -10,9 +10,10 @@ import pytest ...@@ -10,9 +10,10 @@ import pytest
from setuptools.command.test import test from setuptools.command.test import test
from setuptools.dist import Distribution from setuptools.dist import Distribution
from setuptools.tests import ack_2to3
from .textwrap import DALS from .textwrap import DALS
from . import contexts
SETUP_PY = DALS(""" SETUP_PY = DALS("""
from setuptools import setup from setuptools import setup
...@@ -74,6 +75,7 @@ def quiet_log(): ...@@ -74,6 +75,7 @@ def quiet_log():
@pytest.mark.usefixtures('sample_test', 'quiet_log') @pytest.mark.usefixtures('sample_test', 'quiet_log')
@ack_2to3
def test_test(capfd): def test_test(capfd):
params = dict( params = dict(
name='foo', name='foo',
...@@ -124,6 +126,7 @@ def test_tests_are_run_once(capfd): ...@@ -124,6 +126,7 @@ def test_tests_are_run_once(capfd):
@pytest.mark.usefixtures('sample_test') @pytest.mark.usefixtures('sample_test')
@ack_2to3
def test_warns_deprecation(capfd): def test_warns_deprecation(capfd):
params = dict( params = dict(
name='foo', name='foo',
...@@ -149,6 +152,7 @@ def test_warns_deprecation(capfd): ...@@ -149,6 +152,7 @@ def test_warns_deprecation(capfd):
@pytest.mark.usefixtures('sample_test') @pytest.mark.usefixtures('sample_test')
@ack_2to3
def test_deprecation_stderr(capfd): def test_deprecation_stderr(capfd):
params = dict( params = dict(
name='foo', name='foo',
......
...@@ -8,12 +8,21 @@ from pytest_fixture_config import yield_requires_config ...@@ -8,12 +8,21 @@ from pytest_fixture_config import yield_requires_config
import pytest_virtualenv import pytest_virtualenv
from setuptools.extern import six
from .textwrap import DALS from .textwrap import DALS
from .test_easy_install import make_nspkg_sdist from .test_easy_install import make_nspkg_sdist
@pytest.fixture(autouse=True)
def disable_requires_python(monkeypatch):
"""
Disable Requires-Python on Python 2.7
"""
if sys.version_info > (3,):
return
monkeypatch.setenv('PIP_IGNORE_REQUIRES_PYTHON', 'true')
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def pytest_virtualenv_works(virtualenv): def pytest_virtualenv_works(virtualenv):
""" """
...@@ -48,10 +57,7 @@ def test_clean_env_install(bare_virtualenv): ...@@ -48,10 +57,7 @@ def test_clean_env_install(bare_virtualenv):
""" """
Check setuptools can be installed in a clean environment. Check setuptools can be installed in a clean environment.
""" """
bare_virtualenv.run(' && '.join(( bare_virtualenv.run(['python', 'setup.py', 'install'], cd=SOURCE_DIR)
'cd {source}',
'python setup.py install',
)).format(source=SOURCE_DIR))
def _get_pip_versions(): def _get_pip_versions():
...@@ -64,7 +70,7 @@ def _get_pip_versions(): ...@@ -64,7 +70,7 @@ def _get_pip_versions():
from urllib.request import urlopen from urllib.request import urlopen
from urllib.error import URLError from urllib.error import URLError
except ImportError: except ImportError:
from urllib2 import urlopen, URLError # Python 2.7 compat from urllib2 import urlopen, URLError # Python 2.7 compat
try: try:
urlopen('https://pypi.org', timeout=1) urlopen('https://pypi.org', timeout=1)
...@@ -106,10 +112,9 @@ def test_pip_upgrade_from_source(pip_version, virtualenv): ...@@ -106,10 +112,9 @@ def test_pip_upgrade_from_source(pip_version, virtualenv):
dist_dir = virtualenv.workspace dist_dir = virtualenv.workspace
# Generate source distribution / wheel. # Generate source distribution / wheel.
virtualenv.run(' && '.join(( virtualenv.run(' && '.join((
'cd {source}',
'python setup.py -q sdist -d {dist}', 'python setup.py -q sdist -d {dist}',
'python setup.py -q bdist_wheel -d {dist}', 'python setup.py -q bdist_wheel -d {dist}',
)).format(source=SOURCE_DIR, dist=dist_dir)) )).format(dist=dist_dir), cd=SOURCE_DIR)
sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0] sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0]
wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0] wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0]
# Then update from wheel. # Then update from wheel.
...@@ -174,18 +179,20 @@ def _check_test_command_install_requirements(virtualenv, tmpdir): ...@@ -174,18 +179,20 @@ def _check_test_command_install_requirements(virtualenv, tmpdir):
open('success', 'w').close() open('success', 'w').close()
''')) '''))
# Run test command for test package. # Run test command for test package.
virtualenv.run(' && '.join(( virtualenv.run(
'cd {tmpdir}', ['python', 'setup.py', 'test', '-s', 'test'], cd=str(tmpdir))
'python setup.py test -s test',
)).format(tmpdir=tmpdir))
assert tmpdir.join('success').check() assert tmpdir.join('success').check()
def test_test_command_install_requirements(virtualenv, tmpdir): def test_test_command_install_requirements(virtualenv, tmpdir):
# Ensure pip/wheel packages are installed. # Ensure pip/wheel packages are installed.
virtualenv.run("python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") virtualenv.run(
"python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"")
_check_test_command_install_requirements(virtualenv, tmpdir) _check_test_command_install_requirements(virtualenv, tmpdir)
def test_test_command_install_requirements_when_using_easy_install(bare_virtualenv, tmpdir):
def test_test_command_install_requirements_when_using_easy_install(
bare_virtualenv, tmpdir):
_check_test_command_install_requirements(bare_virtualenv, tmpdir) _check_test_command_install_requirements(bare_virtualenv, tmpdir)
...@@ -194,7 +201,5 @@ def test_no_missing_dependencies(bare_virtualenv): ...@@ -194,7 +201,5 @@ def test_no_missing_dependencies(bare_virtualenv):
Quick and dirty test to ensure all external dependencies are vendored. Quick and dirty test to ensure all external dependencies are vendored.
""" """
for command in ('upload',): # sorted(distutils.command.__all__): for command in ('upload',): # sorted(distutils.command.__all__):
bare_virtualenv.run(' && '.join(( bare_virtualenv.run(
'cd {source}', ['python', 'setup.py', command, '-h'], cd=SOURCE_DIR)
'python setup.py {command} -h',
)).format(command=command, source=SOURCE_DIR))
...@@ -125,11 +125,12 @@ def flatten_tree(tree): ...@@ -125,11 +125,12 @@ def flatten_tree(tree):
def format_install_tree(tree): def format_install_tree(tree):
return {x.format( return {
py_version=PY_MAJOR, x.format(
platform=get_platform(), py_version=PY_MAJOR,
shlib_ext=get_config_var('EXT_SUFFIX') or get_config_var('SO')) platform=get_platform(),
for x in tree} shlib_ext=get_config_var('EXT_SUFFIX') or get_config_var('SO'))
for x in tree}
def _check_wheel_install(filename, install_dir, install_tree_includes, def _check_wheel_install(filename, install_dir, install_tree_includes,
...@@ -455,7 +456,8 @@ WHEEL_INSTALL_TESTS = ( ...@@ -455,7 +456,8 @@ WHEEL_INSTALL_TESTS = (
id='empty_namespace_package', id='empty_namespace_package',
file_defs={ file_defs={
'foobar': { 'foobar': {
'__init__.py': "__import__('pkg_resources').declare_namespace(__name__)", '__init__.py':
"__import__('pkg_resources').declare_namespace(__name__)",
}, },
}, },
setup_kwargs=dict( setup_kwargs=dict(
...@@ -579,4 +581,5 @@ def test_wheel_is_compatible(monkeypatch): ...@@ -579,4 +581,5 @@ def test_wheel_is_compatible(monkeypatch):
for t in parse_tag('cp36-cp36m-manylinux1_x86_64'): for t in parse_tag('cp36-cp36m-manylinux1_x86_64'):
yield t yield t
monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags) monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags)
assert Wheel('onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible() assert Wheel(
'onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible()
...@@ -77,7 +77,8 @@ class Wheel: ...@@ -77,7 +77,8 @@ class Wheel:
def is_compatible(self): def is_compatible(self):
'''Is the wheel is compatible with the current platform?''' '''Is the wheel is compatible with the current platform?'''
supported_tags = set((t.interpreter, t.abi, t.platform) for t in sys_tags()) supported_tags = set(
(t.interpreter, t.abi, t.platform) for t in sys_tags())
return next((True for t in self.tags() if t in supported_tags), False) return next((True for t in self.tags() if t in supported_tags), False)
def egg_name(self): def egg_name(self):
......
"""
Finalize the repo for a release. Invokes towncrier and bumpversion.
"""
__requires__ = ['bump2version', 'towncrier']
import subprocess
import pathlib
import re
import sys
def release_kind():
"""
Determine which release to make based on the files in the
changelog.
"""
# use min here as 'major' < 'minor' < 'patch'
return min(
'major' if 'breaking' in file.name else
'minor' if 'change' in file.name else
'patch'
for file in pathlib.Path('changelog.d').iterdir()
)
bump_version_command = [
sys.executable,
'-m', 'bumpversion',
release_kind(),
]
def get_version():
cmd = bump_version_command + ['--dry-run', '--verbose']
out = subprocess.check_output(cmd, text=True)
return re.search('^new_version=(.*)', out, re.MULTILINE).group(1)
def update_changelog():
cmd = [
sys.executable, '-m',
'towncrier',
'--version', get_version(),
'--yes',
]
subprocess.check_call(cmd)
def bump_version():
cmd = bump_version_command + ['--allow-dirty']
subprocess.check_call(cmd)
def ensure_config():
"""
Double-check that Git has an e-mail configured.
"""
subprocess.check_output(['git', 'config', 'user.email'])
def check_changes():
"""
Verify that all of the files in changelog.d have the appropriate
names.
"""
allowed = 'deprecation', 'breaking', 'change', 'doc', 'misc'
assert all(
any(key in file.name for key in allowed)
for file in pathlib.Path('changelog.d').iterdir()
if file.name != '.gitignore'
)
if __name__ == '__main__':
print("Cutting release at", get_version())
ensure_config()
check_changes()
update_changelog()
bump_version()
import os import os
import subprocess import subprocess
import sys import sys
import re
def remove_setuptools(): def remove_setuptools():
...@@ -14,17 +15,68 @@ def remove_setuptools(): ...@@ -14,17 +15,68 @@ def remove_setuptools():
subprocess.check_call(cmd, cwd='.tox') subprocess.check_call(cmd, cwd='.tox')
def pip(args): def bootstrap():
# Honor requires-python when installing test suite dependencies print("Running bootstrap")
if any('-r' in arg for arg in args): cmd = [sys.executable, '-m', 'bootstrap']
os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = '0' subprocess.check_call(cmd)
# When installing '.', remove setuptools
'.' in args and remove_setuptools()
cmd = [sys.executable, '-m', 'pip'] + args def is_install_self(args):
subprocess.check_call(cmd) """
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 disable_python_requires():
"""
On Python 2, install the dependencies that are selective
on Python version while honoring REQUIRES_PYTHON, then
disable REQUIRES_PYTHON so that pip can install this
checkout of setuptools.
"""
pip('install', *test_dependencies())
os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = 'true'
def run(args):
os.environ['PIP_USE_PEP517'] = 'true'
if is_install_self(args):
remove_setuptools()
bootstrap()
sys.version_info > (3,) or disable_python_requires()
pip(*args)
if __name__ == '__main__': if __name__ == '__main__':
pip(sys.argv[1:]) run(sys.argv[1:])
# Note: Run "python bootstrap.py" before running Tox, to generate metadata.
#
# To run Tox against all supported Python interpreters, you can set: # To run Tox against all supported Python interpreters, you can set:
# #
# export TOXENV='py27,py3{5,6,7,8},pypy,pypy3' # export TOXENV='py3{5,6,7,8},pypy,pypy3'
[tox] [tox]
envlist=python envlist=python
minversion = 3.2 minversion = 3.2
requires = requires =
tox-pip-version >= 0.0.6 tox-pip-version >= 0.0.6
# workaround for #1998
virtualenv < 20
[helpers] [helpers]
# Custom pip behavior # Custom pip behavior
pip = python {toxinidir}/tools/tox_pip.py pip = python {toxinidir}/tools/tox_pip.py
[testenv] [testenv]
deps=-r{toxinidir}/tests/requirements.txt
pip_version = pip pip_version = pip
install_command = {[helpers]pip} install {opts} {packages} install_command = {[helpers]pip} install {opts} {packages}
list_dependencies_command = {[helpers]pip} freeze --all list_dependencies_command = {[helpers]pip} freeze --all
setenv = setenv =
COVERAGE_FILE={toxworkdir}/.coverage.{envname} COVERAGE_FILE={toxworkdir}/.coverage.{envname}
py27: PIP_IGNORE_REQUIRES_PYTHON=true
# TODO: The passed environment variables came from copying other tox.ini files # TODO: The passed environment variables came from copying other tox.ini files
# These should probably be individually annotated to explain what needs them. # These should probably be individually annotated to explain what needs them.
passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED passenv=APPDATA HOMEDRIVE HOMEPATH windir Program* CommonProgram* VS* APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED
commands=pytest --cov-config={toxinidir}/tox.ini --cov-report= {posargs} commands=pytest --cov-config={toxinidir}/tox.ini --cov-report= {posargs}
usedevelop=True usedevelop=True
extras =
tests
[testenv:coverage] [testenv:coverage]
...@@ -46,12 +46,12 @@ skip_install=True ...@@ -46,12 +46,12 @@ skip_install=True
commands=codecov -X gcov --file {toxworkdir}/coverage.xml commands=codecov -X gcov --file {toxworkdir}/coverage.xml
[testenv:docs] [testenv:docs]
deps = -r{toxinidir}/docs/requirements.txt extras =
skip_install=True docs
testing
changedir = docs
commands = commands =
python {toxinidir}/bootstrap.py python -m sphinx . {toxinidir}/build/html
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html
sphinx-build -W -b man -d {envtmpdir}/doctrees docs docs/build/man
[coverage:run] [coverage:run]
source= source=
...@@ -60,6 +60,15 @@ source= ...@@ -60,6 +60,15 @@ source=
omit= omit=
*/_vendor/* */_vendor/*
[testenv:finalize]
skip_install = True
deps =
towncrier
bump2version
passenv = *
commands =
python tools/finalize.py
[testenv:release] [testenv:release]
skip_install = True skip_install = True
deps = deps =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment