Commit 0551421f authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

Merge branch 'master' into license-fix-357

parents 28872fc9 78fd7302
pull_request_rules:
- name: auto-merge
conditions:
- base=master
- label=auto-merge
- status-success=continuous-integration/appveyor/pr
- status-success=continuous-integration/travis-ci/pr
- status-success=deploy/netlify
actions:
merge:
method: merge
......@@ -30,7 +30,9 @@ jobs:
install: skip
script: skip
after_success: true
before_deploy: python bootstrap.py
before_deploy:
- python bootstrap.py
- "! grep pyc setuptools.egg-info/SOURCES.txt"
deploy:
provider: pypi
on:
......@@ -58,6 +60,7 @@ install:
# update egg_info based on setup.py in checkout
- python bootstrap.py
- "! grep pyc setuptools.egg-info/SOURCES.txt"
script:
- |
......
v40.7.0
-------
* #1551: File inputs for the `license` field in `setup.cfg` files now explicitly raise an error.
* #1180: Add support for non-ASCII in setup.cfg (#1062). Add support for native strings on some parameters (#1136).
* #1499: ``setuptools.package_index`` no longer relies on the deprecated ``urllib.parse.splituser`` per Python #27485.
* #1544: Added tests for PackageIndex.download (for git URLs).
* #1625: In PEP 517 build_meta builder, ensure that sdists are built as gztar per the spec.
v40.6.3
-------
* #1594: PEP 517 backend no longer declares setuptools as a dependency as it can be assumed.
v40.6.2
-------
* #1592: Fix invalid dependency on external six module (instead of vendored version).
v40.6.1
-------
* #1590: Fixed regression where packages without ``author`` or ``author_email`` fields generated malformed package metadata.
v40.6.0
-------
* #1541: Officially deprecated the ``requires`` parameter in ``setup()``.
* #1519: In ``pkg_resources.normalize_path``, additional path normalization is now performed to ensure path values to a directory is always the same, preventing false positives when checking scripts have a consistent prefix to set up on Windows.
* #1545: Changed the warning class of all deprecation warnings; deprecation warning classes are no longer derived from ``DeprecationWarning`` and are thus visible by default.
* #1554: ``build_meta.build_sdist`` now includes ``setup.py`` in source distributions by default.
* #1576: Started monkey-patching ``get_metadata_version`` and ``read_pkg_file`` onto ``distutils.DistributionMetadata`` to retain the correct version on the ``PKG-INFO`` file in the (deprecated) ``upload`` command.
* #1533: Restricted the ``recursive-include setuptools/_vendor`` to contain only .py and .txt files.
* #1395: Changed Pyrex references to Cython in the documentation.
* #1456: Documented that the ``rpmbuild`` packages is required for the ``bdist_rpm`` command.
* #1537: Documented how to use ``setup.cfg`` for ``src/ layouts``
* #1539: Added minimum version column in ``setup.cfg`` metadata table.
* #1552: Fixed a minor typo in the python 2/3 compatibility documentation.
* #1553: Updated installation instructions to point to ``pip install`` instead of ``ez_setup.py``.
* #1560: Updated ``setuptools`` distribution documentation to remove some outdated information.
* #1564: Documented ``setup.cfg`` minimum version for version and project_urls.
* #1572: Added the ``concurrent.futures`` backport ``futures`` to the Python 2.7 test suite requirements.
v40.5.0
-------
......
......@@ -2,7 +2,7 @@ recursive-include setuptools *.py *.exe *.xml
recursive-include tests *.py
recursive-include setuptools/tests *.html
recursive-include docs *.py *.txt *.conf *.css *.css_t Makefile indexsidebar.html
recursive-include setuptools/_vendor *
recursive-include setuptools/_vendor *.py *.txt
recursive-include pkg_resources *.py *.txt
include *.py
include *.rst
......
......@@ -23,10 +23,6 @@ See the `Installation Instructions
User's Guide for instructions on installing, upgrading, and uninstalling
Setuptools.
The project is `maintained at GitHub <https://github.com/pypa/setuptools>`_
by the `Setuptools Developers
<https://github.com/orgs/pypa/teams/setuptools-developers>`_.
Questions and comments should be directed to the `distutils-sig
mailing list <http://mail.python.org/pipermail/distutils-sig/>`_.
Bug reports and especially tested patches may be
......
......@@ -5,4 +5,11 @@
<h3>Questions? Suggestions? Contributions?</h3>
<p>Visit the <a href="https://github.com/pypa/setuptools">Setuptools project page</a> </p>
<p>Visit the <a href="{{ package_url }}">Project page</a> </p>
<h3 class="donation">Professional support</h3>
<p>
Professionally-supported {{ project }} is available with the
<a href="https://tidelift.com/subscription/pkg/pypi-{{ project }}?utm_source=pypi-{{ project }}&utm_medium=readme">Tidelift Subscription</a>.
</p>
......@@ -67,8 +67,8 @@ All PRs with code changes should include tests. All changes should include a
changelog entry.
``setuptools`` uses `towncrier <https://pypi.org/project/towncrier/>`_
for changelog managment, so when making a PR, please add a news fragment in the
``changelog.d/`` folder. Changelog files are written in Restructured Text and
for changelog management, so when making a PR, please add a news fragment in the
``changelog.d/`` folder. Changelog files are written in reStructuredText and
should be a 1 or 2 sentence description of the substantive changes in the PR.
They should be named ``<pr_number>.<category>.rst``, where the categories are:
......@@ -76,7 +76,7 @@ They should be named ``<pr_number>.<category>.rst``, where the categories are:
- ``breaking``: Any backwards-compatibility breaking change
- ``doc``: A change to the documentation
- ``misc``: Changes internal to the repo like CI, test and build changes
- ``deprecation``: For deprecations of an existing feature of behavior
- ``deprecation``: For deprecations of an existing feature or behavior
A pull request may have more than one of these components, for example a code
change may introduce a new feature that deprecates an old feature, in which
......@@ -89,6 +89,17 @@ code changes. See the following for an example news fragment:
$ cat changelog.d/1288.change.rst
Add support for maintainer in PKG-INFO
-------------------
Auto-Merge Requests
-------------------
To support running all code through CI, even lightweight contributions,
the project employs Mergify to auto-merge pull requests tagged as
auto-merge.
Use ``hub pull-request -l auto-merge`` to create such a pull request
from the command line after pushing a new branch.
-------
Testing
-------
......
:orphan:
``ez_setup`` distribution guide
===============================
Using ``setuptools``... Without bundling it!
---------------------------------------------
.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
.. _EasyInstall Installation Instructions: easy_install.html
.. _Custom Installation Locations: easy_install.html
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
script. (Be sure to add it to your revision control system, too.) Then add
these two lines to the very top of your setup script, before the script imports
anything from setuptools:
.. code-block:: python
import ez_setup
ez_setup.use_setuptools()
That's it. The ``ez_setup`` module will automatically download a matching
version of ``setuptools`` from PyPI, if it isn't present on the target system.
Whenever you install an updated version of setuptools, you should also update
your projects' ``ez_setup.py`` files, so that a matching version gets installed
on the target machine(s).
(By the way, if you need to distribute a specific version of ``setuptools``,
you can specify the exact version and base download URL as parameters to the
``use_setuptools()`` function. See the function's docstring for details.)
What Your Users Should Know
---------------------------
In general, a setuptools-based project looks just like any distutils-based
project -- as long as your users have an internet connection and are installing
to ``site-packages``, that is. But for some users, these conditions don't
apply, and they may become frustrated if this is their first encounter with
a setuptools-based project. To keep these users happy, you should review the
following topics in your project's installation instructions, if they are
relevant to your project and your target audience isn't already familiar with
setuptools and ``easy_install``.
Network Access
If your project is using ``ez_setup``, you should inform users of the
need to either have network access, or to preinstall the correct version of
setuptools using the `EasyInstall installation instructions`_. Those
instructions also have tips for dealing with firewalls as well as how to
manually download and install setuptools.
Custom Installation Locations
You should inform your users that if they are installing your project to
somewhere other than the main ``site-packages`` directory, they should
first install setuptools using the instructions for `Custom Installation
Locations`_, before installing your project.
Your Project's Dependencies
If your project depends on other projects that may need to be downloaded
from PyPI or elsewhere, you should list them in your installation
instructions, or tell users how to find out what they are. While most
users will not need this information, any users who don't have unrestricted
internet access may have to find, download, and install the other projects
manually. (Note, however, that they must still install those projects
using ``easy_install``, or your project will not know they are installed,
and your setup script will try to download them again.)
If you want to be especially friendly to users with limited network access,
you may wish to build eggs for your project and its dependencies, making
them all available for download from your site, or at least create a page
with links to all of the needed eggs. In this way, users with limited
network access can manually download all the eggs to a single directory,
then use the ``-f`` option of ``easy_install`` to specify the directory
to find eggs in. Users who have full network access can just use ``-f``
with the URL of your download page, and ``easy_install`` will find all the
needed eggs using your links directly. This is also useful when your
target audience isn't able to compile packages (e.g. most Windows users)
and your package or some of its dependencies include C code.
Revision Control System Users and Co-Developers
Users and co-developers who are tracking your in-development code using
a revision control system should probably read this manual's sections
regarding such development. Alternately, you may wish to create a
quick-reference guide containing the tips from this manual that apply to
your particular situation. For example, if you recommend that people use
``setup.py develop`` when tracking your in-development code, you should let
them know that this needs to be run after every update or commit.
Similarly, if you remove modules or data files from your project, you
should remind them to run ``setup.py clean --all`` and delete any obsolete
``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not
just setuptools, but not everybody knows about them; be kind to your users
by spelling out your project's best practices rather than leaving them
guessing.)
Creating System Packages
Some users want to manage all Python packages using a single package
manager, and sometimes that package manager isn't ``easy_install``!
Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
``bdist_dumb`` formats for system packaging. If a user has a locally-
installed "bdist" packaging tool that internally uses the distutils
``install`` command, it should be able to work with ``setuptools``. Some
examples of "bdist" formats that this should work with include the
``bdist_nsi`` and ``bdist_msi`` formats for Windows.
However, packaging tools that build binary distributions by running
``setup.py install`` on the command line or as a subprocess will require
modification to work with setuptools. They should use the
``--single-version-externally-managed`` option to the ``install`` command,
combined with the standard ``--root`` or ``--record`` options.
See the `install command`_ documentation below for more details. The
``bdist_deb`` command is an example of a command that currently requires
this kind of patching to work with setuptools.
Please note that building system packages may require you to install
some system software, for example ``bdist_rpm`` requires the ``rpmbuild``
command to be installed.
If you or your users have a problem building a usable system package for
your project, please report the problem via the mailing list so that
either the "bdist" tool in question or setuptools can be modified to
resolve the issue.
Your users might not have ``setuptools`` installed on their machines, or even
if they do, it might not be the right version. Fixing this is easy; just
download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
script. (Be sure to add it to your revision control system, too.) Then add
these two lines to the very top of your setup script, before the script imports
anything from setuptools:
.. code-block:: python
import ez_setup
ez_setup.use_setuptools()
That's it. The ``ez_setup`` module will automatically download a matching
version of ``setuptools`` from PyPI, if it isn't present on the target system.
Whenever you install an updated version of setuptools, you should also update
your projects' ``ez_setup.py`` files, so that a matching version gets installed
on the target machine(s).
(By the way, if you need to distribute a specific version of ``setuptools``,
you can specify the exact version and base download URL as parameters to the
``use_setuptools()`` function. See the function's docstring for details.)
.. _install command:
``install`` - Run ``easy_install`` or old-style installation
============================================================
The setuptools ``install`` command is basically a shortcut to run the
``easy_install`` command on the current project. However, for convenience
in creating "system packages" of setuptools-based projects, you can also
use this option:
``--single-version-externally-managed``
This boolean option tells the ``install`` command to perform an "old style"
installation, with the addition of an ``.egg-info`` directory so that the
installed project will still have its metadata available and operate
normally. If you use this option, you *must* also specify the ``--root``
or ``--record`` options (or both), because otherwise you will have no way
to identify and remove the installed files.
This option is automatically in effect when ``install`` is invoked by another
distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
will create system packages of eggs. It is also automatically in effect if
you specify the ``--root`` option.
``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
==============================================================================
Setuptools runs this command as part of ``install`` operations that use the
``--single-version-externally-managed`` options. You should not invoke it
directly; it is documented here for completeness and so that distutils
extensions such as system package builders can make use of it. This command
has only one option:
``--install-dir=DIR, -d DIR``
The parent directory where the ``.egg-info`` directory will be placed.
Defaults to the same as the ``--install-dir`` option specified for the
``install_lib`` command, which is usually the system ``site-packages``
directory.
This command assumes that the ``egg_info`` command has been given valid options
via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
command and use its options to locate the project's source ``.egg-info``
directory.
......@@ -17,9 +17,9 @@ Documentation content:
:maxdepth: 2
setuptools
easy_install
pkg_resources
python3
development
roadmap
Deprecated: Easy Install <easy_install>
history
......@@ -9,7 +9,7 @@ code.
Setuptools provides a facility to invoke 2to3 on the code as a part of the
build process, by setting the keyword parameter ``use_2to3`` to True, but
the Setuptools strongly recommends instead developing a unified codebase
the Setuptools project strongly recommends instead developing a unified codebase
using `six <https://pypi.org/project/six/>`_,
`future <https://pypi.org/project/future/>`_, or another compatibility
library.
......
sphinx!=1.8.0
rst.linker>=1.9
jaraco.packaging>=3.2
jaraco.packaging>=6.1
setuptools>=34
......@@ -2,11 +2,6 @@
Roadmap
=======
Setuptools has the following large-scale goals on the roadmap:
- Mature declarative config to supersede imperative config in
all supported use-cases and harmonize with pyproject.toml
syntax.
- Deprecate and remove setup_requires and easy_install in
favor of PEP 518 build requirements and pip install.
- Adopt the Distutils package and stop monkeypatching stdlib.
Setuptools maintains a series of `milestones
<https://github.com/pypa/setuptools/milestones>`_ to track
a roadmap of large-scale goals.
This diff is collapsed.
......@@ -238,6 +238,9 @@ __all__ = [
'register_finder', 'register_namespace_handler', 'register_loader_type',
'fixup_namespace_packages', 'get_importer',
# Warnings
'PkgResourcesDeprecationWarning',
# Deprecated/backward compatibility only
'run_main', 'AvailableDistributions',
]
......@@ -2228,7 +2231,7 @@ register_namespace_handler(object, null_ns_handler)
def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes"""
return os.path.normcase(os.path.realpath(_cygwin_patch(filename)))
return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename))))
def _cygwin_patch(filename): # pragma: nocover
......@@ -2335,7 +2338,7 @@ class EntryPoint:
warnings.warn(
"Parameters to load are deprecated. Call .resolve and "
".require separately.",
DeprecationWarning,
PkgResourcesDeprecationWarning,
stacklevel=2,
)
if require:
......@@ -3158,3 +3161,11 @@ def _initialize_master_working_set():
# match order
list(map(working_set.add_entry, sys.path))
globals().update(locals())
class PkgResourcesDeprecationWarning(Warning):
"""
Base class for warning about deprecations in ``pkg_resources``
This class is not derived from ``DeprecationWarning``, and as such is
visible by default.
"""
......@@ -236,3 +236,56 @@ class TestDeepVersionLookupDistutils:
req = pkg_resources.Requirement.parse('foo>=1.9')
dist = pkg_resources.WorkingSet([env.paths['lib']]).find(req)
assert dist.version == version
@pytest.mark.parametrize(
'unnormalized, normalized',
[
('foo', 'foo'),
('foo/', 'foo'),
('foo/bar', 'foo/bar'),
('foo/bar/', 'foo/bar'),
],
)
def test_normalize_path_trailing_sep(self, unnormalized, normalized):
"""Ensure the trailing slash is cleaned for path comparison.
See pypa/setuptools#1519.
"""
result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
result_from_normalized = pkg_resources.normalize_path(normalized)
assert result_from_unnormalized == result_from_normalized
@pytest.mark.skipif(
os.path.normcase('A') != os.path.normcase('a'),
reason='Testing case-insensitive filesystems.',
)
@pytest.mark.parametrize(
'unnormalized, normalized',
[
('MiXeD/CasE', 'mixed/case'),
],
)
def test_normalize_path_normcase(self, unnormalized, normalized):
"""Ensure mixed case is normalized on case-insensitive filesystems.
"""
result_from_unnormalized = pkg_resources.normalize_path(unnormalized)
result_from_normalized = pkg_resources.normalize_path(normalized)
assert result_from_unnormalized == result_from_normalized
@pytest.mark.skipif(
os.path.sep != '\\',
reason='Testing systems using backslashes as path separators.',
)
@pytest.mark.parametrize(
'unnormalized, expected',
[
('forward/slash', 'forward\\slash'),
('forward/slash/', 'forward\\slash'),
('backward\\slash\\', 'backward\\slash'),
],
)
def test_normalize_path_backslash_sep(self, unnormalized, expected):
"""Ensure path seps are cleaned on backslash path sep systems.
"""
result = pkg_resources.normalize_path(unnormalized)
assert result.endswith(expected)
......@@ -15,7 +15,7 @@ import pkg_resources
from pkg_resources import (
parse_requirements, VersionConflict, parse_version,
Distribution, EntryPoint, Requirement, safe_version, safe_name,
WorkingSet)
WorkingSet, PkgResourcesDeprecationWarning)
# from Python 3.6 docs.
......@@ -492,6 +492,15 @@ class TestEntryPoints:
with pytest.raises(ValueError):
EntryPoint.parse_map(self.submap_str)
def testDeprecationWarnings(self):
ep = EntryPoint(
"foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"],
["x"]
)
with pytest.warns(pkg_resources.PkgResourcesDeprecationWarning):
ep.load(require=False)
class TestRequirements:
def testBasics(self):
......
[build-system]
requires = ["wheel"]
build-backend = "setuptools.build_meta"
[tool.towncrier]
package = "setuptools"
......
[bumpversion]
current_version = 40.5.0
current_version = 40.7.0
commit = True
tag = True
......
......@@ -89,7 +89,7 @@ def pypi_link(pkg_filename):
setup_params = dict(
name="setuptools",
version="40.5.0",
version="40.7.0",
description=(
"Easily download, build, install, upgrade, and uninstall "
"Python packages"
......@@ -164,6 +164,7 @@ setup_params = dict(
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Topic :: Software Development :: Libraries :: Python Modules
Topic :: System :: Archiving :: Packaging
Topic :: System :: Systems Administration
......
......@@ -5,10 +5,14 @@ import sys
import functools
import distutils.core
import distutils.filelist
import re
from distutils.errors import DistutilsOptionError
from distutils.util import convert_path
from fnmatch import fnmatchcase
from setuptools.extern.six import PY3
from ._deprecation_warning import SetuptoolsDeprecationWarning
from setuptools.extern.six import PY3, string_types
from setuptools.extern.six.moves import filter, map
import setuptools.version
......@@ -22,6 +26,7 @@ __metaclass__ = type
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
'SetuptoolsDeprecationWarning',
'find_packages'
]
......@@ -158,6 +163,37 @@ class Command(_Command):
_Command.__init__(self, dist)
vars(self).update(kw)
def _ensure_stringlike(self, option, what, default=None):
val = getattr(self, option)
if val is None:
setattr(self, option, default)
return default
elif not isinstance(val, string_types):
raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
% (option, what, val))
return val
def ensure_string_list(self, option):
r"""Ensure that 'option' is a list of strings. If 'option' is
currently a string, we split it either on /,\s*/ or /\s+/, so
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
["foo", "bar", "baz"].
"""
val = getattr(self, option)
if val is None:
return
elif isinstance(val, string_types):
setattr(self, option, re.split(r',\s*|\s+', val))
else:
if isinstance(val, list):
ok = all(isinstance(v, string_types) for v in val)
else:
ok = False
if not ok:
raise DistutilsOptionError(
"'%s' must be a list of strings (got %r)"
% (option, val))
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
......@@ -188,4 +224,5 @@ def findall(dir=os.curdir):
return list(files)
# Apply monkey patches
monkey.patch_all()
class SetuptoolsDeprecationWarning(Warning):
"""
Base class for warning deprecations in ``setuptools``
This class is not derived from ``DeprecationWarning``, and as such is
visible by default.
"""
......@@ -112,12 +112,12 @@ def _get_immediate_subdirectories(a_dir):
def get_requires_for_build_wheel(config_settings=None):
config_settings = _fix_config(config_settings)
return _get_build_requires(config_settings, requirements=['setuptools', 'wheel'])
return _get_build_requires(config_settings, requirements=['wheel'])
def get_requires_for_build_sdist(config_settings=None):
config_settings = _fix_config(config_settings)
return _get_build_requires(config_settings, requirements=['setuptools'])
return _get_build_requires(config_settings, requirements=[])
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
......@@ -149,6 +149,15 @@ def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
return dist_infos[0]
def _file_with_extension(directory, extension):
matching = (
f for f in os.listdir(directory)
if f.endswith(extension)
)
file, = matching
return file
def build_wheel(wheel_directory, config_settings=None,
metadata_directory=None):
config_settings = _fix_config(config_settings)
......@@ -160,23 +169,15 @@ def build_wheel(wheel_directory, config_settings=None,
shutil.rmtree(wheel_directory)
shutil.copytree('dist', wheel_directory)
wheels = [f for f in os.listdir(wheel_directory)
if f.endswith('.whl')]
assert len(wheels) == 1
return wheels[0]
return _file_with_extension(wheel_directory, '.whl')
def build_sdist(sdist_directory, config_settings=None):
config_settings = _fix_config(config_settings)
sdist_directory = os.path.abspath(sdist_directory)
sys.argv = sys.argv[:1] + ['sdist'] + \
sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
config_settings["--global-option"] + \
["--dist-dir", sdist_directory]
_run_setup()
sdists = [f for f in os.listdir(sdist_directory)
if f.endswith('.tar.gz')]
assert len(sdists) == 1
return sdists[0]
return _file_with_extension(sdist_directory, '.tar.gz')
......@@ -7,7 +7,7 @@ import io
from setuptools.extern import six
from pkg_resources import Distribution, PathMetadata, normalize_path
import pkg_resources
from setuptools.command.easy_install import easy_install
from setuptools import namespaces
import setuptools
......@@ -65,9 +65,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
if self.egg_path is None:
self.egg_path = os.path.abspath(ei.egg_base)
target = normalize_path(self.egg_base)
egg_path = normalize_path(os.path.join(self.install_dir,
self.egg_path))
target = pkg_resources.normalize_path(self.egg_base)
egg_path = pkg_resources.normalize_path(
os.path.join(self.install_dir, self.egg_path))
if egg_path != target:
raise DistutilsOptionError(
"--egg-path must be a relative path from the install"
......@@ -75,9 +75,9 @@ class develop(namespaces.DevelopInstaller, easy_install):
)
# Make a distribution for the package's source
self.dist = Distribution(
self.dist = pkg_resources.Distribution(
target,
PathMetadata(target, os.path.abspath(ei.egg_info)),
pkg_resources.PathMetadata(target, os.path.abspath(ei.egg_info)),
project_name=ei.egg_name
)
......@@ -97,13 +97,14 @@ class develop(namespaces.DevelopInstaller, easy_install):
path_to_setup = egg_base.replace(os.sep, '/').rstrip('/')
if path_to_setup != os.curdir:
path_to_setup = '../' * (path_to_setup.count('/') + 1)
resolved = normalize_path(
resolved = pkg_resources.normalize_path(
os.path.join(install_dir, egg_path, path_to_setup)
)
if resolved != normalize_path(os.curdir):
if resolved != pkg_resources.normalize_path(os.curdir):
raise DistutilsOptionError(
"Can't get a consistent path to setup script from"
" installation directory", resolved, normalize_path(os.curdir))
" installation directory", resolved,
pkg_resources.normalize_path(os.curdir))
return path_to_setup
def install_for_development(self):
......@@ -114,7 +115,7 @@ class develop(namespaces.DevelopInstaller, easy_install):
self.reinitialize_command('build_py', inplace=0)
self.run_command('build_py')
bpy_cmd = self.get_finalized_command("build_py")
build_path = normalize_path(bpy_cmd.build_lib)
build_path = pkg_resources.normalize_path(bpy_cmd.build_lib)
# Build extensions
self.reinitialize_command('egg_info', egg_base=build_path)
......@@ -128,7 +129,8 @@ class develop(namespaces.DevelopInstaller, easy_install):
self.egg_path = build_path
self.dist.location = build_path
# XXX
self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info)
self.dist._provider = pkg_resources.PathMetadata(
build_path, ei_cmd.egg_info)
else:
# Without 2to3 inplace works fine:
self.run_command('egg_info')
......@@ -200,6 +202,7 @@ class VersionlessRequirement:
name as the 'requirement' so that scripts will work across
multiple versions.
>>> from pkg_resources import Distribution
>>> dist = Distribution(project_name='foo', version='1.0')
>>> str(dist.as_requirement())
'foo==1.0'
......
......@@ -40,8 +40,11 @@ import subprocess
import shlex
import io
from sysconfig import get_config_vars, get_path
from setuptools import SetuptoolsDeprecationWarning
from setuptools.extern import six
from setuptools.extern.six.moves import configparser, map
......@@ -2077,7 +2080,7 @@ class ScriptWriter:
@classmethod
def get_script_args(cls, dist, executable=None, wininst=False):
# for backward compatibility
warnings.warn("Use get_args", DeprecationWarning)
warnings.warn("Use get_args", EasyInstallDeprecationWarning)
writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
header = cls.get_script_header("", executable, wininst)
return writer.get_args(dist, header)
......@@ -2085,7 +2088,7 @@ class ScriptWriter:
@classmethod
def get_script_header(cls, script_text, executable=None, wininst=False):
# for backward compatibility
warnings.warn("Use get_header", DeprecationWarning, stacklevel=2)
warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2)
if wininst:
executable = "python.exe"
return cls.get_header(script_text, executable)
......@@ -2120,7 +2123,7 @@ class ScriptWriter:
@classmethod
def get_writer(cls, force_windows):
# for backward compatibility
warnings.warn("Use best", DeprecationWarning)
warnings.warn("Use best", EasyInstallDeprecationWarning)
return WindowsScriptWriter.best() if force_windows else cls.best()
@classmethod
......@@ -2152,7 +2155,7 @@ class WindowsScriptWriter(ScriptWriter):
@classmethod
def get_writer(cls):
# for backward compatibility
warnings.warn("Use best", DeprecationWarning)
warnings.warn("Use best", EasyInstallDeprecationWarning)
return cls.best()
@classmethod
......@@ -2333,3 +2336,7 @@ def _patch_usage():
yield
finally:
distutils.core.gen_usage = saved
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
"""Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning."""
......@@ -31,7 +31,7 @@ import setuptools.unicode_utils as unicode_utils
from setuptools.glob import glob
from setuptools.extern import packaging
from setuptools import SetuptoolsDeprecationWarning
def translate_pattern(glob):
"""
......@@ -576,6 +576,12 @@ class manifest_maker(sdist):
self.filelist.extend(rcfiles)
elif os.path.exists(self.manifest):
self.read_manifest()
if os.path.exists("setup.py"):
# setup.py should be included by default, even if it's not
# the script called to create the sdist
self.filelist.append("setup.py")
ei_cmd = self.get_finalized_command('egg_info')
self.filelist.graft(ei_cmd.egg_info)
......@@ -697,7 +703,7 @@ def get_pkg_info_revision():
Get a -r### off of PKG-INFO Version in case this is an sdist of
a subversion revision.
"""
warnings.warn("get_pkg_info_revision is deprecated.", DeprecationWarning)
warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning)
if os.path.exists('PKG-INFO'):
with io.open('PKG-INFO') as f:
for line in f:
......@@ -705,3 +711,7 @@ def get_pkg_info_revision():
if match:
return int(match.group(1))
return 0
class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning):
"""Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning."""
import io
import os
import hashlib
import getpass
from base64 import standard_b64encode
from distutils import log
from distutils.command import upload as orig
from distutils.spawn import spawn
from distutils.errors import DistutilsError
from setuptools.extern.six.moves.urllib.request import urlopen, Request
from setuptools.extern.six.moves.urllib.error import HTTPError
from setuptools.extern.six.moves.urllib.parse import urlparse
class upload(orig.upload):
......@@ -8,7 +21,6 @@ class upload(orig.upload):
Override default upload behavior to obtain password
in a variety of different ways.
"""
def run(self):
try:
orig.upload.run(self)
......@@ -33,6 +45,137 @@ class upload(orig.upload):
self._prompt_for_password()
)
def upload_file(self, command, pyversion, filename):
# Makes sure the repository URL is compliant
schema, netloc, url, params, query, fragments = \
urlparse(self.repository)
if params or query or fragments:
raise AssertionError("Incompatible url %s" % self.repository)
if schema not in ('http', 'https'):
raise AssertionError("unsupported schema " + schema)
# Sign if requested
if self.sign:
gpg_args = ["gpg", "--detach-sign", "-a", filename]
if self.identity:
gpg_args[2:2] = ["--local-user", self.identity]
spawn(gpg_args,
dry_run=self.dry_run)
# Fill in the data - send all the meta-data in case we need to
# register a new release
with open(filename, 'rb') as f:
content = f.read()
meta = self.distribution.metadata
data = {
# action
':action': 'file_upload',
'protocol_version': '1',
# identify release
'name': meta.get_name(),
'version': meta.get_version(),
# file content
'content': (os.path.basename(filename), content),
'filetype': command,
'pyversion': pyversion,
'md5_digest': hashlib.md5(content).hexdigest(),
# additional meta-data
'metadata_version': str(meta.get_metadata_version()),
'summary': meta.get_description(),
'home_page': meta.get_url(),
'author': meta.get_contact(),
'author_email': meta.get_contact_email(),
'license': meta.get_licence(),
'description': meta.get_long_description(),
'keywords': meta.get_keywords(),
'platform': meta.get_platforms(),
'classifiers': meta.get_classifiers(),
'download_url': meta.get_download_url(),
# PEP 314
'provides': meta.get_provides(),
'requires': meta.get_requires(),
'obsoletes': meta.get_obsoletes(),
}
data['comment'] = ''
if self.sign:
data['gpg_signature'] = (os.path.basename(filename) + ".asc",
open(filename+".asc", "rb").read())
# set up the authentication
user_pass = (self.username + ":" + self.password).encode('ascii')
# The exact encoding of the authentication string is debated.
# Anyway PyPI only accepts ascii for both username or password.
auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
# Build up the MIME payload for the POST data
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = b'\r\n--' + boundary.encode('ascii')
end_boundary = sep_boundary + b'--\r\n'
body = io.BytesIO()
for key, value in data.items():
title = '\r\nContent-Disposition: form-data; name="%s"' % key
# handle multiple entries for the same name
if not isinstance(value, list):
value = [value]
for value in value:
if type(value) is tuple:
title += '; filename="%s"' % value[0]
value = value[1]
else:
value = str(value).encode('utf-8')
body.write(sep_boundary)
body.write(title.encode('utf-8'))
body.write(b"\r\n\r\n")
body.write(value)
body.write(end_boundary)
body = body.getvalue()
msg = "Submitting %s to %s" % (filename, self.repository)
self.announce(msg, log.INFO)
# build the Request
headers = {
'Content-type': 'multipart/form-data; boundary=%s' % boundary,
'Content-length': str(len(body)),
'Authorization': auth,
}
request = Request(self.repository, data=body,
headers=headers)
# send the data
try:
result = urlopen(request)
status = result.getcode()
reason = result.msg
except HTTPError as e:
status = e.code
reason = e.msg
except OSError as e:
self.announce(str(e), log.ERROR)
raise
if status == 200:
self.announce('Server response (%s): %s' % (status, reason),
log.INFO)
if self.show_response:
text = getattr(self, '_read_pypi_response',
lambda x: None)(result)
if text is not None:
msg = '\n'.join(('-' * 75, text, '-' * 75))
self.announce(msg, log.INFO)
else:
msg = 'Upload failed (%s): %s' % (status, reason)
self.announce(msg, log.ERROR)
raise DistutilsError(msg)
def _load_password_from_keyring(self):
"""
Attempt to load password from keyring. Suppress Exceptions.
......
......@@ -2,8 +2,12 @@ from __future__ import absolute_import, unicode_literals
import io
import os
import sys
import warnings
import functools
from collections import defaultdict
from functools import partial
from functools import wraps
from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
......@@ -61,6 +65,18 @@ def read_configuration(
return configuration_to_dict(handlers)
def _get_option(target_obj, key):
"""
Given a target object and option key, get that option from
the target object, either through a get_{key} method or
from an attribute directly.
"""
getter_name = 'get_{key}'.format(**locals())
by_attribute = functools.partial(getattr, target_obj, key)
getter = getattr(target_obj, getter_name, by_attribute)
return getter()
def configuration_to_dict(handlers):
"""Returns configuration data gathered by given handlers as a dict.
......@@ -72,20 +88,9 @@ def configuration_to_dict(handlers):
config_dict = defaultdict(dict)
for handler in handlers:
obj_alias = handler.section_prefix
target_obj = handler.target_obj
for option in handler.set_options:
getter = getattr(target_obj, 'get_%s' % option, None)
if getter is None:
value = getattr(target_obj, option)
else:
value = getter()
config_dict[obj_alias][option] = value
value = _get_option(handler.target_obj, option)
config_dict[handler.section_prefix][option] = value
return config_dict
......@@ -110,7 +115,8 @@ def parse_configuration(
options.parse()
meta = ConfigMetadataHandler(
distribution.metadata, command_options, ignore_option_errors, distribution.package_dir)
distribution.metadata, command_options, ignore_option_errors,
distribution.package_dir)
meta.parse()
return meta, options
......@@ -240,6 +246,26 @@ class ConfigHandler:
value = value.lower()
return value in ('1', 'true', 'yes')
@classmethod
def _exclude_files_parser(cls, key):
"""Returns a parser function to make sure field inputs
are not files.
Parses a value after getting the key so error messages are
more informative.
:param key:
:rtype: callable
"""
def parser(value):
exclude_directive = 'file:'
if value.startswith(exclude_directive):
raise ValueError(
'Only strings are accepted for the {0} field, '
'files are not accepted'.format(key))
return value
return parser
@classmethod
def _parse_file(cls, value):
"""Represents value as a string, allowing including text
......@@ -249,7 +275,6 @@ class ConfigHandler:
directory with setup.py.
Examples:
file: LICENSE
file: README.rst, CHANGELOG.md, src/file.txt
:param str value:
......@@ -388,7 +413,7 @@ class ConfigHandler:
section_parser_method = getattr(
self,
# Dots in section names are tranlsated into dunderscores.
# Dots in section names are translated into dunderscores.
('parse_section%s' % method_postfix).replace('.', '__'),
None)
......@@ -399,6 +424,20 @@ class ConfigHandler:
section_parser_method(section_options)
def _deprecated_config_handler(self, func, msg, warning_class):
""" this function will wrap around parameters that are deprecated
:param msg: deprecation message
:param warning_class: class of warning exception to be raised
:param func: function to be wrapped around
"""
@wraps(func)
def config_handler(*args, **kwargs):
warnings.warn(msg, warning_class)
return func(*args, **kwargs)
return config_handler
class ConfigMetadataHandler(ConfigHandler):
......@@ -429,15 +468,20 @@ class ConfigMetadataHandler(ConfigHandler):
parse_list = self._parse_list
parse_file = self._parse_file
parse_dict = self._parse_dict
exclude_files_parser = self._exclude_files_parser
return {
'platforms': parse_list,
'keywords': parse_list,
'provides': parse_list,
'requires': parse_list,
'requires': self._deprecated_config_handler(
parse_list,
"The requires parameter is deprecated, please use "
"install_requires for runtime dependencies.",
DeprecationWarning),
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': parse_file,
'license': exclude_files_parser('license'),
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
......@@ -458,9 +502,12 @@ class ConfigMetadataHandler(ConfigHandler):
# Be strict about versions loaded from file because it's easy to
# accidentally include newlines and other unintended content
if isinstance(parse(version), LegacyVersion):
raise DistutilsOptionError('Version loaded from %s does not comply with PEP 440: %s' % (
value, version
))
tmpl = (
'Version loaded from {value} does not '
'comply with PEP 440: {version}'
)
raise DistutilsOptionError(tmpl.format(**locals()))
return version
version = self._parse_attr(value, self.package_dir)
......@@ -518,12 +565,13 @@ class ConfigOptionsHandler(ConfigHandler):
find_directives = ['find:', 'find_namespace:']
trimmed_value = value.strip()
if not trimmed_value in find_directives:
if trimmed_value not in find_directives:
return self._parse_list(value)
findns = trimmed_value == find_directives[1]
if findns and not PY3:
raise DistutilsOptionError('find_namespace: directive is unsupported on Python < 3.3')
raise DistutilsOptionError(
'find_namespace: directive is unsupported on Python < 3.3')
# Read function arguments from a dedicated section.
find_kwargs = self.parse_section_packages__find(
......
This diff is collapsed.
......@@ -84,7 +84,7 @@ def patch_all():
warehouse = 'https://upload.pypi.org/legacy/'
distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
_patch_distribution_metadata_write_pkg_file()
_patch_distribution_metadata()
# Install Distribution throughout the distutils
for module in distutils.dist, distutils.core, distutils.cmd:
......@@ -101,11 +101,11 @@ def patch_all():
patch_for_msvc_specialized_compiler()
def _patch_distribution_metadata_write_pkg_file():
"""Patch write_pkg_file to also write Requires-Python/Requires-External"""
distutils.dist.DistributionMetadata.write_pkg_file = (
setuptools.dist.write_pkg_file
)
def _patch_distribution_metadata():
"""Patch write_pkg_file and read_pkg_file for higher metadata standards"""
for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'):
new_val = getattr(setuptools.dist, attr)
setattr(distutils.dist.DistributionMetadata, attr, new_val)
def patch_func(replacement, target_mod, func_name):
......
......@@ -850,13 +850,16 @@ class PackageIndex(Environment):
def _download_svn(self, url, filename):
warnings.warn("SVN download support is deprecated", UserWarning)
def splituser(host):
user, delim, host = host.rpartition('@')
return user, host
url = url.split('#', 1)[0] # remove any fragment for svn's sake
creds = ''
if url.lower().startswith('svn:') and '@' in url:
scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
if not netloc and path.startswith('//') and '/' in path[2:]:
netloc, path = path[2:].split('/', 1)
auth, host = urllib.parse.splituser(netloc)
auth, host = splituser(netloc)
if auth:
if ':' in auth:
user, pw = auth.split(':', 1)
......@@ -1047,15 +1050,16 @@ class PyPIConfig(configparser.RawConfigParser):
def open_with_auth(url, opener=urllib.request.urlopen):
"""Open a urllib2 request, handling HTTP authentication"""
scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url)
parsed = urllib.parse.urlparse(url)
scheme, netloc, path, params, query, frag = parsed
# Double scheme does not raise on Mac OS X as revealed by a
# failing test. We would expect "nonnumeric port". Refs #20.
if netloc.endswith(':'):
raise http_client.InvalidURL("nonnumeric port: ''")
if scheme in ('http', 'https'):
auth, host = urllib.parse.splituser(netloc)
if scheme in ('http', 'https') and parsed.username:
auth = ':'.join((parsed.username, parsed.password))
else:
auth = None
......@@ -1068,7 +1072,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
if auth:
auth = "Basic " + _encode_auth(auth)
parts = scheme, host, path, params, query, frag
parts = scheme, parsed.hostname, path, params, query, frag
new_url = urllib.parse.urlunparse(parts)
request = urllib.request.Request(new_url)
request.add_header("Authorization", auth)
......@@ -1082,7 +1086,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
# Put authentication info back into request URL if same host,
# so that links found on the page will work
s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url)
if s2 == scheme and h2 == host:
if s2 == scheme and h2 == parsed.hostname:
parts = s2, netloc, path2, param2, query2, frag2
fp.url = urllib.parse.urlunparse(parts)
......
......@@ -161,7 +161,7 @@ def is_manylinux1_compatible():
def get_darwin_arches(major, minor, machine):
"""Return a list of supported arches (including group arches) for
the given major, minor and machine architecture of an macOS machine.
the given major, minor and machine architecture of a macOS machine.
"""
arches = []
......
import sys
from distutils.errors import DistutilsOptionError
from distutils.util import strtobool
from distutils.debug import DEBUG
class Distribution_parse_config_files:
"""
Mix-in providing forward-compatibility for functionality to be
included by default on Python 3.7.
Do not edit the code in this class except to update functionality
as implemented in distutils.
"""
def parse_config_files(self, filenames=None):
from configparser import ConfigParser
# Ignore install directory options if we have a venv
if sys.prefix != sys.base_prefix:
ignore_options = [
'install-base', 'install-platbase', 'install-lib',
'install-platlib', 'install-purelib', 'install-headers',
'install-scripts', 'install-data', 'prefix', 'exec-prefix',
'home', 'user', 'root']
else:
ignore_options = []
ignore_options = frozenset(ignore_options)
if filenames is None:
filenames = self.find_config_files()
if DEBUG:
self.announce("Distribution.parse_config_files():")
parser = ConfigParser(interpolation=None)
for filename in filenames:
if DEBUG:
self.announce(" reading %s" % filename)
parser.read(filename)
for section in parser.sections():
options = parser.options(section)
opt_dict = self.get_option_dict(section)
for opt in options:
if opt != '__name__' and opt not in ignore_options:
val = parser.get(section,opt)
opt = opt.replace('-', '_')
opt_dict[opt] = (filename, val)
# Make the ConfigParser forget everything (so we retain
# the original filenames that options come from)
parser.__init__()
# If there was a "global" section in the config file, use it
# to set Distribution options.
if 'global' in self.command_options:
for (opt, (src, val)) in self.command_options['global'].items():
alias = self.negative_opt.get(opt)
try:
if alias:
setattr(self, alias, not strtobool(val))
elif opt in ('verbose', 'dry_run'): # ugh!
setattr(self, opt, strtobool(val))
else:
setattr(self, opt, val)
except ValueError as msg:
raise DistutilsOptionError(msg)
if sys.version_info < (3,):
# Python 2 behavior is sufficient
class Distribution_parse_config_files:
pass
if False:
# When updated behavior is available upstream,
# disable override here.
class Distribution_parse_config_files:
pass
......@@ -59,7 +59,7 @@ if not match_hostname:
def _dnsname_match(dn, hostname, max_wildcards=1):
"""Matching according to RFC 6125, section 6.4.3
http://tools.ietf.org/html/rfc6125#section-6.4.3
https://tools.ietf.org/html/rfc6125#section-6.4.3
"""
pats = []
if not dn:
......
......@@ -6,10 +6,13 @@ import pkg_resources.py31compat
def build_files(file_defs, prefix=""):
"""
Build a set of files/directories, as described by the file_defs dictionary.
Build a set of files/directories, as described by the
file_defs dictionary.
Each key/value pair in the dictionary is interpreted as a filename/contents
pair. If the contents value is a dictionary, a directory is created, and the
Each key/value pair in the dictionary is interpreted as
a filename/contents
pair. If the contents value is a dictionary, a directory
is created, and the
dictionary interpreted as the files within it, recursively.
For example:
......
......@@ -19,10 +19,11 @@ class IndexServer(BaseHTTPServer.HTTPServer):
s.stop()
"""
def __init__(self, server_address=('', 0),
def __init__(
self, server_address=('', 0),
RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler):
BaseHTTPServer.HTTPServer.__init__(self, server_address,
RequestHandlerClass)
BaseHTTPServer.HTTPServer.__init__(
self, server_address, RequestHandlerClass)
self._run = True
def start(self):
......@@ -56,10 +57,11 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread):
A simple HTTP Server that records the requests made to it.
"""
def __init__(self, server_address=('', 0),
def __init__(
self, server_address=('', 0),
RequestHandlerClass=RequestRecorder):
BaseHTTPServer.HTTPServer.__init__(self, server_address,
RequestHandlerClass)
BaseHTTPServer.HTTPServer.__init__(
self, server_address, RequestHandlerClass)
threading.Thread.__init__(self)
self.setDaemon(True)
self.requests = []
......
import pytest
import os
import shutil
import mock
from distutils.errors import DistutilsSetupError
......@@ -40,13 +38,14 @@ class TestBuildCLib:
# with that out of the way, let's see if the crude dependency
# system works
cmd.compiler = mock.MagicMock(spec=cmd.compiler)
mock_newer.return_value = ([],[])
mock_newer.return_value = ([], [])
obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
libs = [('example', {'sources': ['example.c'] ,'obj_deps': obj_deps})]
libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})]
cmd.build_libraries(libs)
assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0]
assert [['example.c', 'global.h', 'example.h']] in \
mock_newer.call_args[0]
assert not cmd.compiler.compile.called
assert cmd.compiler.create_static_lib.call_count == 1
......
......@@ -2,16 +2,20 @@ from __future__ import unicode_literals
import os
import shutil
import tarfile
import pytest
from setuptools.build_meta import build_sdist
from .files import build_files
from .textwrap import DALS
from . import py2_only
__metaclass__ = type
futures = pytest.importorskip('concurrent.futures')
importlib = pytest.importorskip('importlib')
# Backports on Python 2.7
import importlib
from concurrent import futures
class BuildBackendBase:
......@@ -108,13 +112,13 @@ def build_backend(tmpdir, request):
def test_get_requires_for_build_wheel(build_backend):
actual = build_backend.get_requires_for_build_wheel()
expected = ['six', 'setuptools', 'wheel']
expected = ['six', 'wheel']
assert sorted(actual) == sorted(expected)
def test_get_requires_for_build_sdist(build_backend):
actual = build_backend.get_requires_for_build_sdist()
expected = ['six', 'setuptools']
expected = ['six']
assert sorted(actual) == sorted(expected)
......@@ -143,7 +147,7 @@ def test_prepare_metadata_for_build_wheel(build_backend):
assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
@pytest.mark.skipif('sys.version_info > (3,)')
@py2_only
def test_prepare_metadata_for_build_wheel_with_str(build_backend):
dist_dir = os.path.abspath(str('pip-dist-info'))
os.makedirs(dist_dir)
......@@ -168,15 +172,67 @@ def test_build_sdist_version_change(build_backend):
sdist_name = build_backend.build_sdist(sdist_into_directory)
assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
# if the setup.py changes subsequent call of the build meta should still succeed, given the
# if the setup.py changes subsequent call of the build meta
# should still succeed, given the
# sdist_directory the frontend specifies is empty
with open(os.path.abspath("setup.py"), 'rt') as file_handler:
content = file_handler.read()
with open(os.path.abspath("setup.py"), 'wt') as file_handler:
file_handler.write(content.replace("version='0.0.0'", "version='0.0.1'"))
file_handler.write(
content.replace("version='0.0.0'", "version='0.0.1'"))
shutil.rmtree(sdist_into_directory)
os.makedirs(sdist_into_directory)
sdist_name = build_backend.build_sdist("out_sdist")
assert os.path.isfile(os.path.join(os.path.abspath("out_sdist"), sdist_name))
assert os.path.isfile(
os.path.join(os.path.abspath("out_sdist"), sdist_name))
def test_build_sdist_setup_py_exists(tmpdir_cwd):
# If build_sdist is called from a script other than setup.py,
# ensure setup.py is include
build_files(defns[0])
targz_path = build_sdist("temp")
with tarfile.open(os.path.join("temp", targz_path)) as tar:
assert any('setup.py' in name for name in tar.getnames())
def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
# Ensure that MANIFEST.in can exclude setup.py
files = {
'setup.py': DALS("""
__import__('setuptools').setup(
name='foo',
version='0.0.0',
py_modules=['hello']
)"""),
'hello.py': '',
'MANIFEST.in': DALS("""
exclude setup.py
""")
}
build_files(files)
targz_path = build_sdist("temp")
with tarfile.open(os.path.join("temp", targz_path)) as tar:
assert not any('setup.py' in name for name in tar.getnames())
def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd):
files = {
'setup.py': DALS("""
__import__('setuptools').setup(
name='foo',
version='0.0.0',
py_modules=['hello']
)"""),
'hello.py': '',
'setup.cfg': DALS("""
[sdist]
formats=zip
""")
}
build_files(files)
build_sdist("temp")
# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import contextlib
import pytest
from distutils.errors import DistutilsOptionError, DistutilsFileError
from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
from setuptools.extern.six.moves.configparser import InterpolationMissingOptionError
from setuptools.tests import is_ascii
from . import py2_only, py3_only
from .textwrap import DALS
class ErrConfigHandler(ConfigHandler):
"""Erroneous handler. Fails to implement required methods."""
......@@ -16,12 +24,12 @@ def make_package_dir(name, base_dir, ns=False):
dir_package = dir_package.mkdir(dir_name)
init_file = None
if not ns:
init_file = dir_package.join('__init__.py')
init_file.write('')
init_file = dir_package.join('__init__.py')
init_file.write('')
return dir_package, init_file
def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
def fake_env(tmpdir, setup_cfg, setup_py=None, encoding='ascii', package_path='fake_package'):
if setup_py is None:
setup_py = (
......@@ -31,7 +39,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
tmpdir.join('setup.py').write(setup_py)
config = tmpdir.join('setup.cfg')
config.write(setup_cfg)
config.write(setup_cfg.encode(encoding), mode='wb')
package_dir, init_file = make_package_dir(package_path, tmpdir)
......@@ -146,6 +154,24 @@ class TestMetadata:
assert metadata.download_url == 'http://test.test.com/test/'
assert metadata.maintainer_email == 'test@test.com'
def test_license_cfg(self, tmpdir):
fake_env(
tmpdir,
DALS("""
[metadata]
name=foo
version=0.0.1
license=Apache 2.0
""")
)
with get_dist(tmpdir) as dist:
metadata = dist.metadata
assert metadata.name == "foo"
assert metadata.version == "0.0.1"
assert metadata.license == "Apache 2.0"
def test_file_mixed(self, tmpdir):
fake_env(
......@@ -288,7 +314,7 @@ class TestMetadata:
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
with pytest.raises(DistutilsOptionError):
with get_dist(tmpdir) as dist:
_ = dist.metadata.version
dist.metadata.version
def test_version_with_package_dir_simple(self, tmpdir):
......@@ -391,6 +417,89 @@ class TestMetadata:
with get_dist(tmpdir) as dist:
assert set(dist.metadata.classifiers) == expected
def test_deprecated_config_handlers(self, tmpdir):
fake_env(
tmpdir,
'[metadata]\n'
'version = 10.1.1\n'
'description = Some description\n'
'requires = some, requirement\n'
)
with pytest.deprecated_call():
with get_dist(tmpdir) as dist:
metadata = dist.metadata
assert metadata.version == '10.1.1'
assert metadata.description == 'Some description'
assert metadata.requires == ['some', 'requirement']
def test_interpolation(self, tmpdir):
fake_env(
tmpdir,
'[metadata]\n'
'description = %(message)s\n'
)
with pytest.raises(InterpolationMissingOptionError):
with get_dist(tmpdir):
pass
skip_if_not_ascii = pytest.mark.skipif(not is_ascii, reason='Test not supported with this locale')
@skip_if_not_ascii
def test_non_ascii_1(self, tmpdir):
fake_env(
tmpdir,
'[metadata]\n'
'description = éàïôñ\n',
encoding='utf-8'
)
with pytest.raises(UnicodeDecodeError):
with get_dist(tmpdir):
pass
def test_non_ascii_2(self, tmpdir):
fake_env(
tmpdir,
'# -*- coding: invalid\n'
)
with pytest.raises(LookupError):
with get_dist(tmpdir):
pass
def test_non_ascii_3(self, tmpdir):
fake_env(
tmpdir,
'\n'
'# -*- coding: invalid\n'
)
with get_dist(tmpdir):
pass
@skip_if_not_ascii
def test_non_ascii_4(self, tmpdir):
fake_env(
tmpdir,
'# -*- coding: utf-8\n'
'[metadata]\n'
'description = éàïôñ\n',
encoding='utf-8'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.description == 'éàïôñ'
@skip_if_not_ascii
def test_non_ascii_5(self, tmpdir):
fake_env(
tmpdir,
'# vim: set fileencoding=iso-8859-15 :\n'
'[metadata]\n'
'description = éàïôñ\n',
encoding='iso-8859-15'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.description == 'éàïôñ'
class TestOptions:
......@@ -414,7 +523,7 @@ class TestOptions:
'tests_require = mock==0.7.2; pytest\n'
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
'dependency_links = http://some.com/here/1, '
'http://some.com/there/2\n'
'http://some.com/there/2\n'
'python_requires = >=1.0, !=2.8\n'
'py_modules = module1, module2\n'
)
......@@ -622,7 +731,7 @@ class TestOptions:
dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True)
with get_dist(tmpdir) as dist:
assert set(dist.packages) == {
assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
}
......@@ -674,7 +783,7 @@ class TestOptions:
tmpdir,
'[options.entry_points]\n'
'group1 = point1 = pack.module:func, '
'.point2 = pack.module2:func_rest [rest]\n'
'.point2 = pack.module2:func_rest [rest]\n'
'group2 = point3 = pack.module:func2\n'
)
......@@ -720,7 +829,10 @@ class TestOptions:
]
assert sorted(dist.data_files) == sorted(expected)
saved_dist_init = _Distribution.__init__
class TestExternalSetters:
# During creation of the setuptools Distribution() object, we call
# the init of the parent distutils Distribution object via
......
......@@ -5,12 +5,12 @@ from setuptools import depends
class TestGetModuleConstant:
def test_basic(self):
"""
Invoke get_module_constant on a module in
the test package.
"""
mod_name = 'setuptools.tests.mod_with_constant'
val = depends.get_module_constant(mod_name, 'value')
assert val == 'three, sir!'
assert 'setuptools.tests.mod_with_constant' not in sys.modules
def test_basic(self):
"""
Invoke get_module_constant on a module in
the test package.
"""
mod_name = 'setuptools.tests.mod_with_constant'
val = depends.get_module_constant(mod_name, 'value')
assert val == 'three, sir!'
assert 'setuptools.tests.mod_with_constant' not in sys.modules
......@@ -3,10 +3,11 @@
from __future__ import unicode_literals
import io
from setuptools.dist import DistDeprecationWarning, _get_unpatched
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
from setuptools.extern import six
from .textwrap import DALS
from .test_easy_install import make_nspkg_sdist
......@@ -56,6 +57,125 @@ def test_dist_fetch_build_egg(tmpdir):
assert [dist.key for dist in resolved_dists if dist] == reqs
def test_dist__get_unpatched_deprecated():
pytest.warns(DistDeprecationWarning, _get_unpatched, [""])
def __read_test_cases():
# Metadata version 1.0
base_attrs = {
"name": "package",
"version": "0.0.1",
"author": "Foo Bar",
"author_email": "foo@bar.net",
"long_description": "Long\ndescription",
"description": "Short description",
"keywords": ["one", "two"]
}
def merge_dicts(d1, d2):
d1 = d1.copy()
d1.update(d2)
return d1
test_cases = [
('Metadata version 1.0', base_attrs.copy()),
('Metadata version 1.1: Provides', merge_dicts(base_attrs, {
'provides': ['package']
})),
('Metadata version 1.1: Obsoletes', merge_dicts(base_attrs, {
'obsoletes': ['foo']
})),
('Metadata version 1.1: Classifiers', merge_dicts(base_attrs, {
'classifiers': [
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'License :: OSI Approved :: MIT License',
]})),
('Metadata version 1.1: Download URL', merge_dicts(base_attrs, {
'download_url': 'https://example.com'
})),
('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, {
'python_requires': '>=3.7'
})),
pytest.param(
'Metadata Version 1.2: Project-Url',
merge_dicts(base_attrs, {
'project_urls': {
'Foo': 'https://example.bar'
}
}), marks=pytest.mark.xfail(
reason="Issue #1578: project_urls not read"
)),
('Metadata Version 2.1: Long Description Content Type',
merge_dicts(base_attrs, {
'long_description_content_type': 'text/x-rst; charset=UTF-8'
})),
pytest.param(
'Metadata Version 2.1: Provides Extra',
merge_dicts(base_attrs, {
'provides_extras': ['foo', 'bar']
}), marks=pytest.mark.xfail(reason="provides_extras not read")),
('Missing author, missing author e-mail',
{'name': 'foo', 'version': '1.0.0'}),
('Missing author',
{'name': 'foo',
'version': '1.0.0',
'author_email': 'snorri@sturluson.name'}),
('Missing author e-mail',
{'name': 'foo',
'version': '1.0.0',
'author': 'Snorri Sturluson'}),
('Missing author',
{'name': 'foo',
'version': '1.0.0',
'author': 'Snorri Sturluson'}),
]
return test_cases
@pytest.mark.parametrize('name,attrs', __read_test_cases())
def test_read_metadata(name, attrs):
dist = Distribution(attrs)
metadata_out = dist.metadata
dist_class = metadata_out.__class__
# Write to PKG_INFO and then load into a new metadata object
if six.PY2:
PKG_INFO = io.BytesIO()
else:
PKG_INFO = io.StringIO()
metadata_out.write_pkg_file(PKG_INFO)
PKG_INFO.seek(0)
metadata_in = dist_class()
metadata_in.read_pkg_file(PKG_INFO)
tested_attrs = [
('name', dist_class.get_name),
('version', dist_class.get_version),
('author', dist_class.get_contact),
('author_email', dist_class.get_contact_email),
('metadata_version', dist_class.get_metadata_version),
('provides', dist_class.get_provides),
('description', dist_class.get_description),
('download_url', dist_class.get_download_url),
('keywords', dist_class.get_keywords),
('platforms', dist_class.get_platforms),
('obsoletes', dist_class.get_obsoletes),
('requires', dist_class.get_requires),
('classifiers', dist_class.get_classifiers),
('project_urls', lambda s: getattr(s, 'project_urls', {})),
('provides_extras', lambda s: getattr(s, 'provides_extras', set())),
]
for attr, getter in tested_attrs:
assert getter(metadata_in) == getter(metadata_out)
def __maintainer_test_cases():
attrs = {"name": "package",
"version": "1.0",
......
......@@ -15,7 +15,9 @@ import distutils.errors
import io
import zipfile
import mock
from setuptools.command.easy_install import (
EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter,
)
import time
from setuptools.extern import six
from setuptools.extern.six.moves import urllib
......@@ -287,6 +289,22 @@ class TestEasyInstallTest:
cmd.easy_install(sdist_script)
assert (target / 'mypkg_script').exists()
def test_dist_get_script_args_deprecated(self):
with pytest.warns(EasyInstallDeprecationWarning):
ScriptWriter.get_script_args(None, None)
def test_dist_get_script_header_deprecated(self):
with pytest.warns(EasyInstallDeprecationWarning):
ScriptWriter.get_script_header("")
def test_dist_get_writer_deprecated(self):
with pytest.warns(EasyInstallDeprecationWarning):
ScriptWriter.get_writer(None)
def test_dist_WindowsScriptWriter_get_writer_deprecated(self):
with pytest.warns(EasyInstallDeprecationWarning):
WindowsScriptWriter.get_writer()
@pytest.mark.filterwarnings('ignore:Unbuilt egg')
class TestPTHFileWriter:
......
import datetime
import sys
import ast
import os
......@@ -7,7 +6,9 @@ import re
import stat
import time
from setuptools.command.egg_info import egg_info, manifest_maker
from setuptools.command.egg_info import (
egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
)
from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
......@@ -148,6 +149,37 @@ class TestEggInfo:
]
assert sorted(actual) == expected
def test_license_is_a_string(self, tmpdir_cwd, env):
setup_config = DALS("""
[metadata]
name=foo
version=0.0.1
license=file:MIT
""")
setup_script = DALS("""
from setuptools import setup
setup()
""")
build_files({'setup.py': setup_script,
'setup.cfg': setup_config})
# This command should fail with a ValueError, but because it's
# currently configured to use a subprocess, the actual traceback
# object is lost and we need to parse it from stderr
with pytest.raises(AssertionError) as exc:
self._run_egg_info_command(tmpdir_cwd, env)
# Hopefully this is not too fragile: the only argument to the
# assertion error should be a traceback, ending with:
# ValueError: ....
#
# assert not 1
tb = exc.value.args[0].split('\n')
assert tb[-3].lstrip().startswith('ValueError')
def test_rebuilt(self, tmpdir_cwd, env):
"""Ensure timestamps are updated when the command is re-run."""
self._create_project()
......@@ -618,6 +650,20 @@ class TestEggInfo:
for msg in fixtures:
assert manifest_maker._should_suppress_warning(msg)
def test_egg_info_includes_setup_py(self, tmpdir_cwd):
self._create_project()
dist = Distribution({"name": "foo", "version": "0.0.1"})
dist.script_name = "non_setup.py"
egg_info_instance = egg_info(dist)
egg_info_instance.finalize_options()
egg_info_instance.run()
assert 'setup.py' in egg_info_instance.filelist.files
with open(egg_info_instance.egg_info + "/SOURCES.txt") as f:
sources = f.read().split('\n')
assert 'setup.py' in sources
def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
environ = os.environ.copy().update(
HOME=env.paths['home'],
......@@ -632,8 +678,8 @@ class TestEggInfo:
data_stream=1,
env=environ,
)
if code:
raise AssertionError(data)
assert not code, data
if output:
assert output in data
......@@ -652,3 +698,52 @@ class TestEggInfo:
with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
pkg_info_lines = pkginfo_file.read().split('\n')
assert 'Version: 0.0.0.dev0' in pkg_info_lines
def test_get_pkg_info_revision_deprecated(self):
pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
EGG_INFO_TESTS = (
# Check for issue #1136: invalid string type when
# reading declarative `setup.cfg` under Python 2.
{
'setup.py': DALS(
"""
from setuptools import setup
setup(
name="foo",
)
"""),
'setup.cfg': DALS(
"""
[options]
package_dir =
= src
"""),
'src': {},
},
# Check Unicode can be used in `setup.py` under Python 2.
{
'setup.py': DALS(
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from setuptools import setup, find_packages
setup(
name="foo",
package_dir={'': 'src'},
)
"""),
'src': {},
}
)
@pytest.mark.parametrize('package_files', EGG_INFO_TESTS)
def test_egg_info(self, tmpdir_cwd, env, package_files):
"""
"""
build_files(package_files)
code, data = environment.run_setup_py(
cmd=['egg_info'],
data_stream=1,
)
assert not code, data
......@@ -12,10 +12,10 @@ from . import py3_only
from setuptools.extern.six import PY3
from setuptools import find_packages
if PY3:
from setuptools import find_namespace_packages
from setuptools import find_namespace_packages
# modeled after CPython's test.support.can_symlink
# modeled after CPython's test.support.can_symlink
def can_symlink():
TESTFN = tempfile.mktemp()
symlink_path = TESTFN + "can_symlink"
......@@ -164,12 +164,14 @@ class TestFindPackages:
def test_pep420_ns_package_no_includes(self):
packages = find_namespace_packages(
self.dist_dir, exclude=['pkg.subpkg.assets'])
self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
self._assert_packages(
packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
@py3_only
def test_pep420_ns_package_no_includes_or_excludes(self):
packages = find_namespace_packages(self.dist_dir)
expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
expected = [
'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
self._assert_packages(packages, expected)
@py3_only
......@@ -185,4 +187,3 @@ class TestFindPackages:
shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
packages = find_namespace_packages(self.dist_dir)
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
......@@ -64,7 +64,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
def test_executable_with_spaces_escaping_unix(self, tmpdir):
"""
Ensure that shebang on Unix is not quoted, even when a value with spaces
Ensure that shebang on Unix is not quoted, even when
a value with spaces
is specified using --executable.
"""
expected = '#!%s\n' % self.unix_spaces_exe
......@@ -77,7 +78,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
def test_executable_arg_escaping_win32(self, tmpdir):
"""
Ensure that shebang on Windows is quoted when getting a path with spaces
Ensure that shebang on Windows is quoted when
getting a path with spaces
from --executable, that is itself properly quoted.
"""
expected = '#!"%s"\n' % self.win32_exe
......
......@@ -6,6 +6,11 @@ Try to install a few packages.
import glob
import os
import sys
import re
import subprocess
import functools
import tarfile
import zipfile
from setuptools.extern.six.moves import urllib
import pytest
......@@ -114,15 +119,12 @@ def test_pyuri(install_context):
assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex'))
import re
import subprocess
import functools
import tarfile, zipfile
build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
@pytest.mark.parametrize("build_dep", build_deps)
@pytest.mark.skipif(sys.version_info < (3, 6), reason='run only on late versions')
@pytest.mark.skipif(
sys.version_info < (3, 6), reason='run only on late versions')
def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
"""
All setuptools build dependencies must build without
......@@ -149,13 +151,16 @@ def install(pkg_dir, install_dir):
breaker.write('raise ImportError()')
cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir]
env = dict(os.environ, PYTHONPATH=pkg_dir)
output = subprocess.check_output(cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
output = subprocess.check_output(
cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
return output.decode('utf-8')
def download_and_extract(request, req, target):
cmd = [sys.executable, '-m', 'pip', 'download', '--no-deps',
'--no-binary', ':all:', req]
cmd = [
sys.executable, '-m', 'pip', 'download', '--no-deps',
'--no-binary', ':all:', req,
]
output = subprocess.check_output(cmd, encoding='utf-8')
filename = re.search('Saved (.*)', output).group(1)
request.addfinalizer(functools.partial(os.remove, filename))
......
This diff is collapsed.
......@@ -49,7 +49,8 @@ def mock_reg(hkcu=None, hklm=None):
for k in hive if k.startswith(key.lower())
)
return mock.patch.multiple(distutils.msvc9compiler.Reg,
return mock.patch.multiple(
distutils.msvc9compiler.Reg,
read_keys=read_keys, read_values=read_values)
......@@ -61,7 +62,7 @@ class TestModulePatch:
"""
key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir'
key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir'
key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft')
def test_patched(self):
"Test the module is actually patched"
......
from __future__ import absolute_import, unicode_literals
import os
import sys
import subprocess
......@@ -12,7 +11,7 @@ from setuptools.command import test
class TestNamespaces:
@pytest.mark.xfail(
@pytest.mark.skipif(
sys.version_info < (3, 5),
reason="Requires importlib.util.module_from_spec",
)
......
......@@ -6,6 +6,8 @@ import distutils.errors
from setuptools.extern import six
from setuptools.extern.six.moves import urllib, http_client
import mock
import pytest
import pkg_resources
import setuptools.package_index
......@@ -42,7 +44,10 @@ class TestPackageIndex:
hosts=('www.example.com',)
)
url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk'
url = (
'url:%20https://svn.plone.org/svn'
'/collective/inquant.contentmirror.plone/trunk'
)
try:
v = index.open_url(url)
except Exception as v:
......@@ -61,9 +66,9 @@ class TestPackageIndex:
index.opener = _urlopen
url = 'http://example.com'
try:
v = index.open_url(url)
except Exception as v:
assert 'line' in str(v)
index.open_url(url)
except Exception as exc:
assert 'line' in str(exc)
else:
raise AssertionError('Should have raise here!')
......@@ -81,7 +86,11 @@ class TestPackageIndex:
index.open_url(url)
except distutils.errors.DistutilsError as error:
msg = six.text_type(error)
assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg
assert (
'nonnumeric port' in msg
or 'getaddrinfo failed' in msg
or 'Name or service not known' in msg
)
return
raise RuntimeError("Did not raise")
......@@ -223,6 +232,61 @@ class TestPackageIndex:
assert dists[0].version == ''
assert dists[1].version == vc
def test_download_git_with_rev(self, tmpdir):
url = 'git+https://github.example/group/project@master#egg=foo'
index = setuptools.package_index.PackageIndex()
with mock.patch("os.system") as os_system_mock:
result = index.download(url, str(tmpdir))
os_system_mock.assert_called()
expected_dir = str(tmpdir / 'project@master')
expected = (
'git clone --quiet '
'https://github.example/group/project {expected_dir}'
).format(**locals())
first_call_args = os_system_mock.call_args_list[0][0]
assert first_call_args == (expected,)
tmpl = '(cd {expected_dir} && git checkout --quiet master)'
expected = tmpl.format(**locals())
assert os_system_mock.call_args_list[1][0] == (expected,)
assert result == expected_dir
def test_download_git_no_rev(self, tmpdir):
url = 'git+https://github.example/group/project#egg=foo'
index = setuptools.package_index.PackageIndex()
with mock.patch("os.system") as os_system_mock:
result = index.download(url, str(tmpdir))
os_system_mock.assert_called()
expected_dir = str(tmpdir / 'project')
expected = (
'git clone --quiet '
'https://github.example/group/project {expected_dir}'
).format(**locals())
os_system_mock.assert_called_once_with(expected)
def test_download_svn(self, tmpdir):
url = 'svn+https://svn.example/project#egg=foo'
index = setuptools.package_index.PackageIndex()
with pytest.warns(UserWarning):
with mock.patch("os.system") as os_system_mock:
result = index.download(url, str(tmpdir))
os_system_mock.assert_called()
expected_dir = str(tmpdir / 'project')
expected = (
'svn checkout -q '
'svn+https://svn.example/project {expected_dir}'
).format(**locals())
os_system_mock.assert_called_once_with(expected)
class TestContentCheckers:
def test_md5(self):
......
......@@ -32,7 +32,9 @@ class TestPEP425Tags:
if sys.version_info < (3, 3):
config_vars.update({'Py_UNICODE_SIZE': 2})
mock_gcf = self.mock_get_config_var(**config_vars)
with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf):
with patch(
'setuptools.pep425tags.sysconfig.get_config_var',
mock_gcf):
abi_tag = pep425tags.get_abi_tag()
assert abi_tag == base + flags
......
......@@ -26,7 +26,8 @@ class TestSandbox:
"""
It should be possible to execute a setup.py with a Byte Order Mark
"""
target = pkg_resources.resource_filename(__name__,
target = pkg_resources.resource_filename(
__name__,
'script-with-bom.py')
namespace = types.ModuleType('namespace')
setuptools.sandbox._execfile(target, vars(namespace))
......
......@@ -20,8 +20,8 @@ from setuptools.command.egg_info import manifest_maker
from setuptools.dist import Distribution
from setuptools.tests import fail_on_ascii
from .text import Filenames
from . import py3_only
py3_only = pytest.mark.xfail(six.PY2, reason="Test runs on Python 3 only")
SETUP_ATTRS = {
'name': 'sdist_test',
......@@ -92,9 +92,8 @@ fail_on_latin1_encoded_filenames = pytest.mark.xfail(
class TestSdistTest:
def setup_method(self, method):
self.temp_dir = tempfile.mkdtemp()
f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
f.write(SETUP_PY)
f.close()
with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f:
f.write(SETUP_PY)
# Set up the rest of the test package
test_pkg = os.path.join(self.temp_dir, 'sdist_test')
......@@ -135,6 +134,47 @@ class TestSdistTest:
assert os.path.join('sdist_test', 'c.rst') not in manifest
assert os.path.join('d', 'e.dat') in manifest
def test_setup_py_exists(self):
dist = Distribution(SETUP_ATTRS)
dist.script_name = 'foo.py'
cmd = sdist(dist)
cmd.ensure_finalized()
with quiet():
cmd.run()
manifest = cmd.filelist.files
assert 'setup.py' in manifest
def test_setup_py_missing(self):
dist = Distribution(SETUP_ATTRS)
dist.script_name = 'foo.py'
cmd = sdist(dist)
cmd.ensure_finalized()
if os.path.exists("setup.py"):
os.remove("setup.py")
with quiet():
cmd.run()
manifest = cmd.filelist.files
assert 'setup.py' not in manifest
def test_setup_py_excluded(self):
with open("MANIFEST.in", "w") as manifest_file:
manifest_file.write("exclude setup.py")
dist = Distribution(SETUP_ATTRS)
dist.script_name = 'foo.py'
cmd = sdist(dist)
cmd.ensure_finalized()
with quiet():
cmd.run()
manifest = cmd.filelist.files
assert 'setup.py' not in manifest
def test_defaults_case_sensitivity(self):
"""
Make sure default files (README.*, etc.) are added in a case-sensitive
......
......@@ -77,7 +77,8 @@ class TestDepends:
from json import __version__
assert dep.get_module_constant('json', '__version__') == __version__
assert dep.get_module_constant('sys', 'version') == sys.version
assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__
assert dep.get_module_constant(
'setuptools.tests.test_setuptools', '__doc__') == __doc__
@needs_bytecode
def testRequire(self):
......@@ -216,7 +217,8 @@ class TestFeatures:
self.req = Require('Distutils', '1.0.3', 'distutils')
self.dist = makeSetup(
features={
'foo': Feature("foo", standard=True, require_features=['baz', self.req]),
'foo': Feature(
"foo", standard=True, require_features=['baz', self.req]),
'bar': Feature("bar", standard=True, packages=['pkg.bar'],
py_modules=['bar_et'], remove=['bar.ext'],
),
......@@ -252,7 +254,8 @@ class TestFeatures:
('with-dwim', None, 'include DWIM') in dist.feature_options
)
assert (
('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options
('without-dwim', None, 'exclude DWIM (default)')
in dist.feature_options
)
assert (
('with-bar', None, 'include bar (default)') in dist.feature_options
......
......@@ -4,7 +4,6 @@ from __future__ import unicode_literals
from distutils import log
import os
import sys
import pytest
......@@ -93,10 +92,6 @@ def test_test(capfd):
assert out == 'Foo\n'
@pytest.mark.xfail(
sys.version_info < (2, 7),
reason="No discover support for unittest on Python 2.6",
)
@pytest.mark.usefixtures('tmpdir_cwd', 'quiet_log')
def test_tests_are_run_once(capfd):
params = dict(
......
import mock
import os
import re
from distutils import log
from distutils.errors import DistutilsError
import pytest
from setuptools.command.upload import upload
from setuptools.dist import Distribution
from setuptools.extern import six
def _parse_upload_body(body):
boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
entries = []
name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"')
for entry in body.split(boundary):
pair = entry.split(u'\r\n\r\n')
if not len(pair) == 2:
continue
key, value = map(six.text_type.strip, pair)
m = name_re.match(key)
if m is not None:
key = m.group(1)
entries.append((key, value))
return entries
@pytest.fixture
def patched_upload(tmpdir):
class Fix:
def __init__(self, cmd, urlopen):
self.cmd = cmd
self.urlopen = urlopen
def __iter__(self):
return iter((self.cmd, self.urlopen))
def get_uploaded_metadata(self):
request = self.urlopen.call_args_list[0][0][0]
body = request.data.decode('utf-8')
entries = dict(_parse_upload_body(body))
return entries
class ResponseMock(mock.Mock):
def getheader(self, name, default=None):
"""Mocked getheader method for response object"""
return {
'content-type': 'text/plain; charset=utf-8',
}.get(name.lower(), default)
with mock.patch('setuptools.command.upload.urlopen') as urlopen:
urlopen.return_value = ResponseMock()
urlopen.return_value.getcode.return_value = 200
urlopen.return_value.read.return_value = b''
content = os.path.join(str(tmpdir), "content_data")
with open(content, 'w') as f:
f.write("Some content")
dist = Distribution()
dist.dist_files = [('sdist', '3.7.0', content)]
cmd = upload(dist)
cmd.announce = mock.Mock()
cmd.username = 'user'
cmd.password = 'hunter2'
yield Fix(cmd, urlopen)
class TestUploadTest:
def test_upload_metadata(self, patched_upload):
cmd, patch = patched_upload
# Set the metadata version to 2.1
cmd.distribution.metadata.metadata_version = '2.1'
# Run the command
cmd.ensure_finalized()
cmd.run()
# Make sure we did the upload
patch.assert_called_once()
# Make sure the metadata version is correct in the headers
entries = patched_upload.get_uploaded_metadata()
assert entries['metadata_version'] == '2.1'
def test_warns_deprecation(self):
dist = Distribution()
dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
......@@ -41,3 +128,86 @@ class TestUploadTest:
"upload instead (https://pypi.org/p/twine/)",
log.WARN
)
@pytest.mark.parametrize('url', [
'https://example.com/a;parameter', # Has parameters
'https://example.com/a?query', # Has query
'https://example.com/a#fragment', # Has fragment
'ftp://example.com', # Invalid scheme
])
def test_upload_file_invalid_url(self, url, patched_upload):
patched_upload.urlopen.side_effect = Exception("Should not be reached")
cmd = patched_upload.cmd
cmd.repository = url
cmd.ensure_finalized()
with pytest.raises(AssertionError):
cmd.run()
def test_upload_file_http_error(self, patched_upload):
patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError(
'https://example.com',
404,
'File not found',
None,
None
)
cmd = patched_upload.cmd
cmd.ensure_finalized()
with pytest.raises(DistutilsError):
cmd.run()
cmd.announce.assert_any_call(
'Upload failed (404): File not found',
log.ERROR)
def test_upload_file_os_error(self, patched_upload):
patched_upload.urlopen.side_effect = OSError("Invalid")
cmd = patched_upload.cmd
cmd.ensure_finalized()
with pytest.raises(OSError):
cmd.run()
cmd.announce.assert_any_call('Invalid', log.ERROR)
@mock.patch('setuptools.command.upload.spawn')
def test_upload_file_gpg(self, spawn, patched_upload):
cmd, urlopen = patched_upload
cmd.sign = True
cmd.identity = "Alice"
cmd.dry_run = True
content_fname = cmd.distribution.dist_files[0][2]
signed_file = content_fname + '.asc'
with open(signed_file, 'wb') as f:
f.write("signed-data".encode('utf-8'))
cmd.ensure_finalized()
cmd.run()
# Make sure that GPG was called
spawn.assert_called_once_with([
"gpg", "--detach-sign", "--local-user", "Alice", "-a",
content_fname
], dry_run=True)
# Read the 'signed' data that was transmitted
entries = patched_upload.get_uploaded_metadata()
assert entries['gpg_signature'] == 'signed-data'
def test_show_response_no_error(self, patched_upload):
# This test is just that show_response doesn't throw an error
# It is not really important what the printed response looks like
# in a deprecated command, but we don't want to introduce new
# errors when importing this function from distutils
patched_upload.cmd.show_response = True
patched_upload.cmd.ensure_finalized()
patched_upload.cmd.run()
......@@ -57,9 +57,6 @@ def test_pip_upgrade_from_source(virtualenv):
Check pip can upgrade setuptools from source.
"""
dist_dir = virtualenv.workspace
if sys.version_info < (2, 7):
# Python 2.6 support was dropped in wheel 0.30.0.
virtualenv.run('pip install -U "wheel<0.30.0"')
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
'cd {source}',
......@@ -137,3 +134,14 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir):
'python setup.py test -s test',
)).format(tmpdir=tmpdir))
assert tmpdir.join('success').check()
def test_no_missing_dependencies(bare_virtualenv):
"""
Quick and dirty test to ensure all external dependencies are vendored.
"""
for command in ('upload',): # sorted(distutils.command.__all__):
bare_virtualenv.run(' && '.join((
'cd {source}',
'python setup.py {command} -h',
)).format(command=command, source=SOURCE_DIR))
......@@ -63,6 +63,7 @@ WHEEL_INFO_TESTS = (
}),
)
@pytest.mark.parametrize(
('filename', 'info'), WHEEL_INFO_TESTS,
ids=[t[0] for t in WHEEL_INFO_TESTS]
......@@ -487,6 +488,7 @@ WHEEL_INSTALL_TESTS = (
)
@pytest.mark.parametrize(
'params', WHEEL_INSTALL_TESTS,
ids=list(params['id'] for params in WHEEL_INSTALL_TESTS),
......
......@@ -97,7 +97,8 @@ class TestCLI(WrapperTester):
'arg 4\\',
'arg5 a\\\\b',
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
......@@ -134,7 +135,11 @@ class TestCLI(WrapperTester):
with (tmpdir / 'foo-script.py').open('w') as f:
f.write(self.prep_script(tmpl))
cmd = [str(tmpdir / 'foo.exe')]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
......@@ -172,7 +177,9 @@ class TestGUI(WrapperTester):
str(tmpdir / 'test_output.txt'),
'Test Argument',
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
assert not stdout
assert not stderr
......
import unicodedata
import sys
import re
from setuptools.extern import six
......@@ -42,3 +43,15 @@ def try_encode(string, enc):
return string.encode(enc)
except UnicodeEncodeError:
return None
CODING_RE = re.compile(br'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)')
def detect_encoding(fp):
first_line = fp.readline()
fp.seek(0)
m = CODING_RE.match(first_line)
if m is None:
return None
return m.group(1).decode('ascii')
......@@ -8,10 +8,11 @@ import posixpath
import re
import zipfile
from pkg_resources import Distribution, PathMetadata, parse_version
import pkg_resources
import setuptools
from pkg_resources import parse_version
from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.extern.six import PY3
from setuptools import Distribution as SetuptoolsDistribution
from setuptools import pep425tags
from setuptools.command.egg_info import write_requirements
......@@ -79,7 +80,7 @@ class Wheel:
return next((True for t in self.tags() if t in supported_tags), False)
def egg_name(self):
return Distribution(
return pkg_resources.Distribution(
project_name=self.project_name, version=self.version,
platform=(None if self.platform == 'any' else get_platform()),
).egg_name() + '.egg'
......@@ -130,9 +131,9 @@ class Wheel:
zf.extractall(destination_eggdir)
# Convert metadata.
dist_info = os.path.join(destination_eggdir, dist_info)
dist = Distribution.from_location(
dist = pkg_resources.Distribution.from_location(
destination_eggdir, dist_info,
metadata=PathMetadata(destination_eggdir, dist_info),
metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info),
)
# Note: Evaluate and strip markers now,
......@@ -155,7 +156,7 @@ class Wheel:
os.path.join(egg_info, 'METADATA'),
os.path.join(egg_info, 'PKG-INFO'),
)
setup_dist = SetuptoolsDistribution(
setup_dist = setuptools.Distribution(
attrs=dict(
install_requires=install_requires,
extras_require=extras_require,
......
......@@ -8,7 +8,7 @@ import subprocess
from distutils.command.install import INSTALL_SCHEMES
from string import Template
from six.moves import urllib
from setuptools.extern.six.moves import urllib
def _system_call(*args):
......
......@@ -3,8 +3,11 @@ pytest-flake8; python_version!="3.4"
pytest-flake8<=1.0.0; python_version=="3.4"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
pytest>=3.0.2
# pytest pinned to <4 due to #1638
pytest>=3.7,<4
wheel
coverage>=4.5.1
pytest-cov>=2.5.1
paver; python_version>="3.6"
futures; python_version=="2.7"
pip==18.1 # Temporary workaround for #1644.
......@@ -12,7 +12,7 @@ deps=-rtests/requirements.txt
# Changed from default (`python -m pip ...`)
# to prevent the current working directory
# from being added to `sys.path`.
install_command={envbindir}/pip install {opts} {packages}
install_command=python -c 'import sys; sys.path.remove(""); from pkg_resources import load_entry_point; load_entry_point("pip", "console_scripts", "pip")()' install {opts} {packages}
# Same as above.
list_dependencies_command={envbindir}/pip freeze
setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname}
......
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