From 0c79e4d09cf429a2aabbcb6d4cf1455ec45a0137 Mon Sep 17 00:00:00 2001 From: Alexander Duryagin Date: Fri, 11 Jan 2019 15:29:40 +0300 Subject: include pyproject.toml in sdist (#1632) --- setuptools/command/py36compat.py | 2 +- setuptools/tests/test_sdist.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/setuptools/command/py36compat.py b/setuptools/command/py36compat.py index 61063e75..c256bfb8 100644 --- a/setuptools/command/py36compat.py +++ b/setuptools/command/py36compat.py @@ -76,7 +76,7 @@ class sdist_add_defaults: self.warn("standard file '%s' not found" % fn) def _add_defaults_optional(self): - optional = ['test/test*.py', 'setup.cfg'] + optional = ['test/test*.py', 'setup.cfg', 'pyproject.toml'] for pattern in optional: files = filter(os.path.isfile, glob(pattern)) self.filelist.extend(files) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index d2c4e0cf..06813a00 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -449,6 +449,20 @@ class TestSdistTest: except UnicodeDecodeError: filename not in cmd.filelist.files + def test_pyproject_toml_in_sdist(self): + """ + Check if pyproject.toml is included in source distribution if present + """ + open(os.path.join(self.temp_dir, 'pyproject.toml'), 'w').close() + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + with quiet(): + cmd.run() + manifest = cmd.filelist.files + assert 'pyproject.toml' in manifest + def test_default_revctrl(): """ -- cgit v1.2.3 From bc976ed7b9a2e0594c5822316ae63e64723a7ed6 Mon Sep 17 00:00:00 2001 From: Alexander Duryagin Date: Fri, 11 Jan 2019 15:33:18 +0300 Subject: add changelog fragment --- changelog.d/1634.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1634.change.rst diff --git a/changelog.d/1634.change.rst b/changelog.d/1634.change.rst new file mode 100644 index 00000000..ab544239 --- /dev/null +++ b/changelog.d/1634.change.rst @@ -0,0 +1 @@ +Include ``pyproject.toml`` in source distribution by default -- cgit v1.2.3 From 1d6bcf1730a8490a2a16fe2eac73a5437f743dd2 Mon Sep 17 00:00:00 2001 From: Alexander Duryagin Date: Fri, 11 Jan 2019 15:34:32 +0300 Subject: add trailing dot in changelog fragment --- changelog.d/1634.change.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/1634.change.rst b/changelog.d/1634.change.rst index ab544239..27d0a64a 100644 --- a/changelog.d/1634.change.rst +++ b/changelog.d/1634.change.rst @@ -1 +1 @@ -Include ``pyproject.toml`` in source distribution by default +Include ``pyproject.toml`` in source distribution by default. -- cgit v1.2.3 From d53e024af2f5d8f3a4a36588c3dc004d156bc830 Mon Sep 17 00:00:00 2001 From: Alexander Duryagin Date: Fri, 11 Jan 2019 15:57:54 +0300 Subject: do not change py36compat, put changes into sdist command --- setuptools/command/py36compat.py | 2 +- setuptools/command/sdist.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/setuptools/command/py36compat.py b/setuptools/command/py36compat.py index c256bfb8..61063e75 100644 --- a/setuptools/command/py36compat.py +++ b/setuptools/command/py36compat.py @@ -76,7 +76,7 @@ class sdist_add_defaults: self.warn("standard file '%s' not found" % fn) def _add_defaults_optional(self): - optional = ['test/test*.py', 'setup.cfg', 'pyproject.toml'] + optional = ['test/test*.py', 'setup.cfg'] for pattern in optional: files = filter(os.path.isfile, glob(pattern)) self.filelist.extend(files) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index bcfae4d8..40965a67 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -121,6 +121,14 @@ class sdist(sdist_add_defaults, orig.sdist): if has_leaky_handle: read_template = __read_template_hack + def _add_defaults_optional(self): + if six.PY2: + sdist_add_defaults._add_defaults_optional(self) + else: + super()._add_defaults_optional() + if os.path.isfile('pyproject.toml'): + self.filelist.append('pyproject.toml') + def _add_defaults_python(self): """getting python files""" if self.distribution.has_pure_modules(): -- cgit v1.2.3 From 4a6b8ba7ced6bb841000a59bdef7f9879fb6578d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 17 Feb 2019 15:42:23 -0500 Subject: Add test capturing expectation that provides_extras are ordered. --- setuptools/tests/test_dist.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 390c3dfc..e349d068 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -263,3 +263,16 @@ def test_maintainer_author(name, attrs, tmpdir): else: line = '%s: %s' % (fkey, val) assert line in pkg_lines_set + + +def test_provides_extras_deterministic_order(): + attrs = dict(extras_require=dict( + a=['foo'], + b=['bar'], + )) + dist = Distribution(attrs) + assert dist.metadata.provides_extras == ['a', 'b'] + attrs['extras_require'] = dict( + reversed(list(attrs['extras_require'].items()))) + dist = Distribution(attrs) + assert dist.metadata.provides_extras == ['b', 'a'] -- cgit v1.2.3 From 3d289a7015b9ba4f8dd29594309e472fd880841e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 17 Feb 2019 17:01:57 -0500 Subject: Add 'ordered_set' as a vendored package --- setuptools/_vendor/ordered_set.py | 488 ++++++++++++++++++++++++++++++++++++++ setuptools/_vendor/vendored.txt | 1 + setuptools/extern/__init__.py | 2 +- 3 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 setuptools/_vendor/ordered_set.py diff --git a/setuptools/_vendor/ordered_set.py b/setuptools/_vendor/ordered_set.py new file mode 100644 index 00000000..d257470b --- /dev/null +++ b/setuptools/_vendor/ordered_set.py @@ -0,0 +1,488 @@ +""" +An OrderedSet is a custom MutableSet that remembers its order, so that every +entry has an index that can be looked up. + +Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger, +and released under the MIT license. +""" +import itertools as it +from collections import deque + +try: + # Python 3 + from collections.abc import MutableSet, Sequence +except ImportError: + # Python 2.7 + from collections import MutableSet, Sequence + +SLICE_ALL = slice(None) +__version__ = "3.1" + + +def is_iterable(obj): + """ + Are we being asked to look up a list of things, instead of a single thing? + We check for the `__iter__` attribute so that this can cover types that + don't have to be known by this module, such as NumPy arrays. + + Strings, however, should be considered as atomic values to look up, not + iterables. The same goes for tuples, since they are immutable and therefore + valid entries. + + We don't need to check for the Python 2 `unicode` type, because it doesn't + have an `__iter__` attribute anyway. + """ + return ( + hasattr(obj, "__iter__") + and not isinstance(obj, str) + and not isinstance(obj, tuple) + ) + + +class OrderedSet(MutableSet, Sequence): + """ + An OrderedSet is a custom MutableSet that remembers its order, so that + every entry has an index that can be looked up. + + Example: + >>> OrderedSet([1, 1, 2, 3, 2]) + OrderedSet([1, 2, 3]) + """ + + def __init__(self, iterable=None): + self.items = [] + self.map = {} + if iterable is not None: + self |= iterable + + def __len__(self): + """ + Returns the number of unique elements in the ordered set + + Example: + >>> len(OrderedSet([])) + 0 + >>> len(OrderedSet([1, 2])) + 2 + """ + return len(self.items) + + def __getitem__(self, index): + """ + Get the item at a given index. + + If `index` is a slice, you will get back that slice of items, as a + new OrderedSet. + + If `index` is a list or a similar iterable, you'll get a list of + items corresponding to those indices. This is similar to NumPy's + "fancy indexing". The result is not an OrderedSet because you may ask + for duplicate indices, and the number of elements returned should be + the number of elements asked for. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset[1] + 2 + """ + if isinstance(index, slice) and index == SLICE_ALL: + return self.copy() + elif hasattr(index, "__index__") or isinstance(index, slice): + result = self.items[index] + if isinstance(result, list): + return self.__class__(result) + else: + return result + elif is_iterable(index): + return [self.items[i] for i in index] + else: + raise TypeError("Don't know how to index an OrderedSet by %r" % index) + + def copy(self): + """ + Return a shallow copy of this object. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> other = this.copy() + >>> this == other + True + >>> this is other + False + """ + return self.__class__(self) + + def __getstate__(self): + if len(self) == 0: + # The state can't be an empty list. + # We need to return a truthy value, or else __setstate__ won't be run. + # + # This could have been done more gracefully by always putting the state + # in a tuple, but this way is backwards- and forwards- compatible with + # previous versions of OrderedSet. + return (None,) + else: + return list(self) + + def __setstate__(self, state): + if state == (None,): + self.__init__([]) + else: + self.__init__(state) + + def __contains__(self, key): + """ + Test if the item is in this ordered set + + Example: + >>> 1 in OrderedSet([1, 3, 2]) + True + >>> 5 in OrderedSet([1, 3, 2]) + False + """ + return key in self.map + + def add(self, key): + """ + Add `key` as an item to this OrderedSet, then return its index. + + If `key` is already in the OrderedSet, return the index it already + had. + + Example: + >>> oset = OrderedSet() + >>> oset.append(3) + 0 + >>> print(oset) + OrderedSet([3]) + """ + if key not in self.map: + self.map[key] = len(self.items) + self.items.append(key) + return self.map[key] + + append = add + + def update(self, sequence): + """ + Update the set with the given iterable sequence, then return the index + of the last element inserted. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.update([3, 1, 5, 1, 4]) + 4 + >>> print(oset) + OrderedSet([1, 2, 3, 5, 4]) + """ + item_index = None + try: + for item in sequence: + item_index = self.add(item) + except TypeError: + raise ValueError( + "Argument needs to be an iterable, got %s" % type(sequence) + ) + return item_index + + def index(self, key): + """ + Get the index of a given entry, raising an IndexError if it's not + present. + + `key` can be an iterable of entries that is not a string, in which case + this returns a list of indices. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.index(2) + 1 + """ + if is_iterable(key): + return [self.index(subkey) for subkey in key] + return self.map[key] + + # Provide some compatibility with pd.Index + get_loc = index + get_indexer = index + + def pop(self): + """ + Remove and return the last element from the set. + + Raises KeyError if the set is empty. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.pop() + 3 + """ + if not self.items: + raise KeyError("Set is empty") + + elem = self.items[-1] + del self.items[-1] + del self.map[elem] + return elem + + def discard(self, key): + """ + Remove an element. Do not raise an exception if absent. + + The MutableSet mixin uses this to implement the .remove() method, which + *does* raise an error when asked to remove a non-existent item. + + Example: + >>> oset = OrderedSet([1, 2, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + >>> oset.discard(2) + >>> print(oset) + OrderedSet([1, 3]) + """ + if key in self: + i = self.map[key] + del self.items[i] + del self.map[key] + for k, v in self.map.items(): + if v >= i: + self.map[k] = v - 1 + + def clear(self): + """ + Remove all items from this OrderedSet. + """ + del self.items[:] + self.map.clear() + + def __iter__(self): + """ + Example: + >>> list(iter(OrderedSet([1, 2, 3]))) + [1, 2, 3] + """ + return iter(self.items) + + def __reversed__(self): + """ + Example: + >>> list(reversed(OrderedSet([1, 2, 3]))) + [3, 2, 1] + """ + return reversed(self.items) + + def __repr__(self): + if not self: + return "%s()" % (self.__class__.__name__,) + return "%s(%r)" % (self.__class__.__name__, list(self)) + + def __eq__(self, other): + """ + Returns true if the containers have the same items. If `other` is a + Sequence, then order is checked, otherwise it is ignored. + + Example: + >>> oset = OrderedSet([1, 3, 2]) + >>> oset == [1, 3, 2] + True + >>> oset == [1, 2, 3] + False + >>> oset == [2, 3] + False + >>> oset == OrderedSet([3, 2, 1]) + False + """ + # In Python 2 deque is not a Sequence, so treat it as one for + # consistent behavior with Python 3. + if isinstance(other, (Sequence, deque)): + # Check that this OrderedSet contains the same elements, in the + # same order, as the other object. + return list(self) == list(other) + try: + other_as_set = set(other) + except TypeError: + # If `other` can't be converted into a set, it's not equal. + return False + else: + return set(self) == other_as_set + + def union(self, *sets): + """ + Combines all unique items. + Each items order is defined by its first appearance. + + Example: + >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0]) + >>> print(oset) + OrderedSet([3, 1, 4, 5, 2, 0]) + >>> oset.union([8, 9]) + OrderedSet([3, 1, 4, 5, 2, 0, 8, 9]) + >>> oset | {10} + OrderedSet([3, 1, 4, 5, 2, 0, 10]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + containers = map(list, it.chain([self], sets)) + items = it.chain.from_iterable(containers) + return cls(items) + + def __and__(self, other): + # the parent implementation of this is backwards + return self.intersection(other) + + def intersection(self, *sets): + """ + Returns elements in common between all sets. Order is defined only + by the first set. + + Example: + >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3]) + >>> print(oset) + OrderedSet([1, 2, 3]) + >>> oset.intersection([2, 4, 5], [1, 2, 3, 4]) + OrderedSet([2]) + >>> oset.intersection() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + if sets: + common = set.intersection(*map(set, sets)) + items = (item for item in self if item in common) + else: + items = self + return cls(items) + + def difference(self, *sets): + """ + Returns all elements that are in this set but not the others. + + Example: + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2])) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3])) + OrderedSet([1]) + >>> OrderedSet([1, 2, 3]) - OrderedSet([2]) + OrderedSet([1, 3]) + >>> OrderedSet([1, 2, 3]).difference() + OrderedSet([1, 2, 3]) + """ + cls = self.__class__ + if sets: + other = set.union(*map(set, sets)) + items = (item for item in self if item not in other) + else: + items = self + return cls(items) + + def issubset(self, other): + """ + Report whether another set contains this set. + + Example: + >>> OrderedSet([1, 2, 3]).issubset({1, 2}) + False + >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4}) + True + >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5}) + False + """ + if len(self) > len(other): # Fast check for obvious cases + return False + return all(item in other for item in self) + + def issuperset(self, other): + """ + Report whether this set contains another set. + + Example: + >>> OrderedSet([1, 2]).issuperset([1, 2, 3]) + False + >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3}) + True + >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3}) + False + """ + if len(self) < len(other): # Fast check for obvious cases + return False + return all(item in self for item in other) + + def symmetric_difference(self, other): + """ + Return the symmetric difference of two OrderedSets as a new set. + That is, the new set will contain all elements that are in exactly + one of the sets. + + Their order will be preserved, with elements from `self` preceding + elements from `other`. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference(other) + OrderedSet([4, 5, 9, 2]) + """ + cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet + diff1 = cls(self).difference(other) + diff2 = cls(other).difference(self) + return diff1.union(diff2) + + def _update_items(self, items): + """ + Replace the 'items' list of this OrderedSet with a new one, updating + self.map accordingly. + """ + self.items = items + self.map = {item: idx for (idx, item) in enumerate(items)} + + def difference_update(self, *sets): + """ + Update this OrderedSet to remove items from one or more other sets. + + Example: + >>> this = OrderedSet([1, 2, 3]) + >>> this.difference_update(OrderedSet([2, 4])) + >>> print(this) + OrderedSet([1, 3]) + + >>> this = OrderedSet([1, 2, 3, 4, 5]) + >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6])) + >>> print(this) + OrderedSet([3, 5]) + """ + items_to_remove = set() + for other in sets: + items_to_remove |= set(other) + self._update_items([item for item in self.items if item not in items_to_remove]) + + def intersection_update(self, other): + """ + Update this OrderedSet to keep only items in another set, preserving + their order in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.intersection_update(other) + >>> print(this) + OrderedSet([1, 3, 7]) + """ + other = set(other) + self._update_items([item for item in self.items if item in other]) + + def symmetric_difference_update(self, other): + """ + Update this OrderedSet to remove items from another set, then + add items from the other set that were not present in this set. + + Example: + >>> this = OrderedSet([1, 4, 3, 5, 7]) + >>> other = OrderedSet([9, 7, 1, 3, 2]) + >>> this.symmetric_difference_update(other) + >>> print(this) + OrderedSet([4, 5, 9, 2]) + """ + items_to_add = [item for item in other if item not in self] + items_to_remove = set(other) + self._update_items( + [item for item in self.items if item not in items_to_remove] + items_to_add + ) diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index 7d77863d..379aae56 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -1,3 +1,4 @@ packaging==16.8 pyparsing==2.2.1 six==1.10.0 +ordered-set diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py index cb2fa329..e8c616f9 100644 --- a/setuptools/extern/__init__.py +++ b/setuptools/extern/__init__.py @@ -69,5 +69,5 @@ class VendorImporter: sys.meta_path.append(self) -names = 'six', 'packaging', 'pyparsing', +names = 'six', 'packaging', 'pyparsing', 'ordered_set', VendorImporter(__name__, names, 'setuptools._vendor').install() -- cgit v1.2.3 From 636070d9922f1443ae98b0fdd45b6c74c3c9af11 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 17 Feb 2019 17:09:26 -0500 Subject: Use an ordered set when constructing 'extras provided'. Ref #1305. --- setuptools/dist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index ddb1787a..ce37761e 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -28,6 +28,7 @@ from distutils.version import StrictVersion from setuptools.extern import six from setuptools.extern import packaging +from setuptools.extern import ordered_set from setuptools.extern.six.moves import map, filter, filterfalse from . import SetuptoolsDeprecationWarning @@ -408,7 +409,7 @@ class Distribution(_Distribution): _DISTUTILS_UNSUPPORTED_METADATA = { 'long_description_content_type': None, 'project_urls': dict, - 'provides_extras': set, + 'provides_extras': ordered_set.OrderedSet, } _patched_dist = None -- cgit v1.2.3 From 7f7780e5b572d6fcacd63bf99389dd9f48c5345c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 17 Feb 2019 17:37:01 -0500 Subject: In tests, force deterministic ordering on extras_require so tests pass. --- setuptools/tests/test_dist.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index e349d068..0c0b9d66 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals import io +import collections + from setuptools.dist import DistDeprecationWarning, _get_unpatched from setuptools import Distribution from setuptools.extern.six.moves.urllib.request import pathname2url @@ -266,13 +268,13 @@ def test_maintainer_author(name, attrs, tmpdir): def test_provides_extras_deterministic_order(): - attrs = dict(extras_require=dict( - a=['foo'], - b=['bar'], - )) + extras = collections.OrderedDict() + extras['a'] = ['foo'] + extras['b'] = ['bar'] + attrs = dict(extras_require=extras) dist = Distribution(attrs) assert dist.metadata.provides_extras == ['a', 'b'] - attrs['extras_require'] = dict( + attrs['extras_require'] = collections.OrderedDict( reversed(list(attrs['extras_require'].items()))) dist = Distribution(attrs) assert dist.metadata.provides_extras == ['b', 'a'] -- cgit v1.2.3 From 8f227af516c8c6b991c8e6c76f5bf4672f36c41e Mon Sep 17 00:00:00 2001 From: Emiel Wiedijk Date: Sat, 23 Feb 2019 20:51:09 +0100 Subject: Set sys.argv[0] in build scripts run by build_meta Some setup.py scripts, use sys.argv[0] to locate the source directory of a project. I added this to build_meta.__legacy__ since that is focused on backwards compatibility with old scripts. However, @pganssle said this behaviour should not be added to setuptools.build_meta. Fixes #1628 --- setuptools/build_meta.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 10c4b528..eb9e815e 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -232,6 +232,12 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): if script_dir not in sys.path: 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: super(_BuildMetaLegacyBackend, self).run_setup(setup_script=setup_script) @@ -242,6 +248,7 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): # the original path so that the path manipulation does not persist # within the hook after run_setup is called. sys.path[:] = sys_path + sys.argv[0] = sys_argv_0 # The primary backend _BACKEND = _BuildMetaBackend() -- cgit v1.2.3 From 0eff214d94ced853ded99c036c6d792c889e47ab Mon Sep 17 00:00:00 2001 From: Emiel Wiedijk Date: Sat, 23 Feb 2019 21:06:45 +0100 Subject: Add changelog entry --- changelog.d/1704.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1704.change.rst diff --git a/changelog.d/1704.change.rst b/changelog.d/1704.change.rst new file mode 100644 index 00000000..62450835 --- /dev/null +++ b/changelog.d/1704.change.rst @@ -0,0 +1 @@ +Set sys.argv[0] in setup script run by build_meta.__legacy__ -- cgit v1.2.3 From b2701fb39252fc68b4f0b22c3a3b79039f4cc58e Mon Sep 17 00:00:00 2001 From: Emiel Wiedijk Date: Sat, 23 Feb 2019 22:19:43 +0100 Subject: Add tests --- setuptools/tests/test_build_meta.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index e1efe561..41a40789 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -385,6 +385,28 @@ class TestBuildMetaBackend: 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): backend_name = 'setuptools.build_meta:__legacy__' @@ -396,3 +418,9 @@ class TestBuildMetaLegacyBackend(TestBuildMetaBackend): build_backend = self.get_build_backend() build_backend.build_sdist("temp") + + def test_sys_argv_passthrough(self, tmpdir_cwd): + build_files(self._sys_argv_0_passthrough) + + build_backend = self.get_build_backend() + build_backend.build_sdist("temp") -- cgit v1.2.3 From 880ff4a3579bac7839f8f038085381ae14155f31 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 16 May 2019 12:46:50 +0200 Subject: Force metadata-version = 1.2 when project urls are present. Closes: #1756 --- setuptools/dist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index 9a165de0..ea6411b1 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -54,7 +54,8 @@ def get_metadata_version(self): mv = StrictVersion('2.1') elif (self.maintainer is not None or self.maintainer_email is not None or - getattr(self, 'python_requires', None) is not None): + getattr(self, 'python_requires', None) is not None or + self.project_urls): mv = StrictVersion('1.2') elif (self.provides or self.requires or self.obsoletes or self.classifiers or self.download_url): -- cgit v1.2.3 From a64ddf0d2f2bcd6e9843fb55c94fba922f315722 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Thu, 16 May 2019 13:48:15 +0200 Subject: Added test for metadata-version 1.2 --- setuptools/tests/test_egg_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index db9c3873..316eb2ed 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -622,6 +622,7 @@ class TestEggInfo: assert expected_line in pkg_info_lines expected_line = 'Project-URL: Link Two, https://example.com/two/' assert expected_line in pkg_info_lines + assert 'Metadata-Version: 1.2' in pkg_info_lines def test_python_requires_egg_info(self, tmpdir_cwd, env): self._setup_script_with_requires( -- cgit v1.2.3 From 5e8927a17f4e1a5b7e97cbe42639dbcba21acb69 Mon Sep 17 00:00:00 2001 From: Bastian Venthur Date: Fri, 17 May 2019 08:22:55 +0200 Subject: Added changelog entry. --- changelog.d/1756.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1756.change.rst diff --git a/changelog.d/1756.change.rst b/changelog.d/1756.change.rst new file mode 100644 index 00000000..5c908d35 --- /dev/null +++ b/changelog.d/1756.change.rst @@ -0,0 +1 @@ +Forse metadata-version >= 1.2. when project urls are present. -- cgit v1.2.3 From ca1f766ee16c3a504082a95f2afd5a58fcae7d47 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 28 May 2019 21:38:56 +0200 Subject: tests: unpin pytest The new releases for pytest-fixture-config and pytest-shutil are compatible with pytest>=4.0. --- tests/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index f944df27..cb3e6726 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,8 +3,7 @@ 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 pinned to <4 due to #1638 -pytest>=3.7,<4 +pytest>=3.7 wheel coverage>=4.5.1 pytest-cov>=2.5.1 -- cgit v1.2.3 From 59dd72d9df7a88d692637a8c488aa884c182e557 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Sat, 1 Jun 2019 20:33:32 +0200 Subject: Use license classifiers rather than the license field. The license field has a 'free' format, while the classifiers are unique identifiers, similar to SPDX identifiers. In the documentation, we should therefore showcase the use of classifiers. --- changelog.d/1776.doc.rst | 1 + docs/setuptools.txt | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog.d/1776.doc.rst diff --git a/changelog.d/1776.doc.rst b/changelog.d/1776.doc.rst new file mode 100644 index 00000000..d4f1dbca --- /dev/null +++ b/changelog.d/1776.doc.rst @@ -0,0 +1 @@ +Use license classifiers rather than the license field. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 64b385cb..2e7fe3bd 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -136,16 +136,18 @@ dependencies, and perhaps some data files and scripts:: author="Me", author_email="me@example.com", description="This is an Example Package", - license="PSF", keywords="hello world example examples", url="http://example.com/HelloWorld/", # project home page, if any project_urls={ "Bug Tracker": "https://bugs.example.com/HelloWorld/", "Documentation": "https://docs.example.com/HelloWorld/", "Source Code": "https://code.example.com/HelloWorld/", - } + }, + classifiers=[ + 'License :: OSI Approved :: Python Software Foundation License' + ] - # could also include long_description, download_url, classifiers, etc. + # could also include long_description, download_url, etc. ) In the sections that follow, we'll explain what most of these ``setup()`` @@ -2234,6 +2236,7 @@ boilerplate code in some cases. license = BSD 3-Clause License classifiers = Framework :: Django + License :: OSI Approved :: BSD License Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 -- cgit v1.2.3 From 8aeff6b2c9a64e47ad2a22533d7e65c08cd4103f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 10 Jun 2019 13:03:33 -0400 Subject: Update developer docs to describe motivation behind vendored dependencies. Ref #1781. --- docs/developer-guide.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt index a5942c8b..d145fba1 100644 --- a/docs/developer-guide.txt +++ b/docs/developer-guide.txt @@ -137,3 +137,17 @@ To build the docs locally, use tox:: .. _Sphinx: http://www.sphinx-doc.org/en/master/ .. _published documentation: https://setuptools.readthedocs.io/en/latest/ + +--------------------- +Vendored Dependencies +--------------------- + +Setuptools has some dependencies, but due to `bootstrapping issues +`, those dependencies +cannot be declared as they won't be resolved soon enough to build +setuptools from source. Eventually, this limitation may be lifted as +PEP 517/518 reach ubiquitous adoption, but for now, Setuptools +cannot declare dependencies other than through +``setuptools/_vendor/vendored.txt`` and +``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of +``paver update_vendored`` (pavement.py). -- cgit v1.2.3 From fa22b42d9f2f8a15568dd3a3d290e33c9be86796 Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Thu, 13 Jun 2019 18:31:57 +0200 Subject: launcher: Fix build with mingw-w64 execv() requires process.h to be included according to the MSVC documentation but for some reason it also works without it. mingw-w64 on the other hand fails to build the launcher if the include isn't there, so add it. --- launcher.c | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher.c b/launcher.c index be69f0c6..23ef3ac2 100644 --- a/launcher.c +++ b/launcher.c @@ -37,6 +37,7 @@ #include #include #include +#include int child_pid=0; -- cgit v1.2.3 From 53b8db359378f436bfd88f90a90aaf01b650d3a6 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Tue, 18 Jun 2019 16:15:25 +0900 Subject: Stop using deprecated HTMLParser.unescape HTMLParser.unescape is accessed even when unused - this will cause an exception when `HTMLParser.unescape` is removed in Python 3.9. --- changelog.d/1788.change.rst | 1 + setuptools/py33compat.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1788.change.rst diff --git a/changelog.d/1788.change.rst b/changelog.d/1788.change.rst new file mode 100644 index 00000000..d8a49fd4 --- /dev/null +++ b/changelog.d/1788.change.rst @@ -0,0 +1 @@ +Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9. diff --git a/setuptools/py33compat.py b/setuptools/py33compat.py index 87cf5398..cb694436 100644 --- a/setuptools/py33compat.py +++ b/setuptools/py33compat.py @@ -52,4 +52,8 @@ class Bytecode_compat: Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) -unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) +unescape = getattr(html, 'unescape', None) +if unescape is None: + # HTMLParser.unescape is deprecated since Python 3.4, and will be removed + # from 3.9. + unescape = html_parser.HTMLParser().unescape -- cgit v1.2.3 From 10fe6be3d61414bd6cd9ed723e961d7ea8f5fd61 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sun, 30 Jun 2019 14:24:41 +0200 Subject: tests: fix `test_distribution_version_missing` to work with pytest>=5.0 --- pkg_resources/tests/test_pkg_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index fb77c685..a0f2c452 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -242,7 +242,7 @@ def test_distribution_version_missing(tmpdir, suffix, expected_filename, with pytest.raises(ValueError) as excinfo: dist.version - err = str(excinfo) + err = str(excinfo.value) # Include a string expression after the assert so the full strings # will be visible for inspection on failure. assert expected_text in err, str((expected_text, err)) -- cgit v1.2.3 From 4598c1a2a918f2f74e7242df775308841e477038 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sun, 30 Jun 2019 14:25:12 +0200 Subject: tests: tweak default pytest arguments to fix Python 3.8 support --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 1c5b6b09..612fb91f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] -addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX +addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .* flake8-ignore = setuptools/site-patch.py F821 -- cgit v1.2.3 From 67344c95b9402b4720d3c9e61d01096a6453efa6 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 13 Jul 2019 03:19:50 -0700 Subject: Fix #1790 : Include the file path in get_metadata() UnicodeDecodeErrors (#1791) Include the file path in get_metadata() UnicodeDecodeErrors. --- changelog.d/1790.change.rst | 2 ++ pkg_resources/__init__.py | 13 ++++++-- pkg_resources/tests/test_pkg_resources.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1790.change.rst diff --git a/changelog.d/1790.change.rst b/changelog.d/1790.change.rst new file mode 100644 index 00000000..e4a7998d --- /dev/null +++ b/changelog.d/1790.change.rst @@ -0,0 +1,2 @@ +Added the file path to the error message when a ``UnicodeDecodeError`` occurs +while reading a metadata file. diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 97e08d68..1f170cfd 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -1416,8 +1416,17 @@ class NullProvider: def get_metadata(self, name): if not self.egg_info: return "" - value = self._get(self._fn(self.egg_info, name)) - return value.decode('utf-8') if six.PY3 else value + path = self._get_metadata_path(name) + value = self._get(path) + if six.PY2: + return value + try: + return value.decode('utf-8') + except UnicodeDecodeError as exc: + # Include the path in the error message to simplify + # troubleshooting, and without changing the exception type. + exc.reason += ' in {} file at path: {}'.format(name, path) + raise def get_metadata_lines(self, name): return yield_lines(self.get_metadata(name)) diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index a0f2c452..5960868a 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -18,6 +18,7 @@ except ImportError: import mock from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution +from setuptools.extern import six from pkg_resources.extern.six.moves import map from pkg_resources.extern.six import text_type, string_types @@ -191,6 +192,59 @@ class TestResourceManager: subprocess.check_call(cmd) +def make_test_distribution(metadata_path, metadata): + """ + Make a test Distribution object, and return it. + + :param metadata_path: the path to the metadata file that should be + created. This should be inside a distribution directory that should + also be created. For example, an argument value might end with + ".dist-info/METADATA". + :param metadata: the desired contents of the metadata file, as bytes. + """ + dist_dir = os.path.dirname(metadata_path) + os.mkdir(dist_dir) + with open(metadata_path, 'wb') as f: + f.write(metadata) + dists = list(pkg_resources.distributions_from_metadata(dist_dir)) + dist, = dists + + return dist + + +def test_get_metadata__bad_utf8(tmpdir): + """ + Test a metadata file with bytes that can't be decoded as utf-8. + """ + filename = 'METADATA' + # Convert the tmpdir LocalPath object to a string before joining. + metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename) + # Encode a non-ascii string with the wrong encoding (not utf-8). + metadata = 'née'.encode('iso-8859-1') + dist = make_test_distribution(metadata_path, metadata=metadata) + + if six.PY2: + # In Python 2, get_metadata() doesn't do any decoding. + actual = dist.get_metadata(filename) + assert actual == metadata + return + + # Otherwise, we are in the Python 3 case. + with pytest.raises(UnicodeDecodeError) as excinfo: + dist.get_metadata(filename) + + exc = excinfo.value + actual = str(exc) + expected = ( + # The error message starts with "'utf-8' codec ..." However, the + # spelling of "utf-8" can vary (e.g. "utf8") so we don't include it + "codec can't decode byte 0xe9 in position 1: " + 'invalid continuation byte in METADATA file at path: ' + ) + assert expected in actual, 'actual: {}'.format(actual) + assert actual.endswith(metadata_path), 'actual: {}'.format(actual) + + # TODO: remove this in favor of Path.touch() when Python 2 is dropped. def touch_file(path): """ -- cgit v1.2.3 From 305bb1cefc3251c67b55149139a768ddf474f7b6 Mon Sep 17 00:00:00 2001 From: Daniel Himmelstein Date: Thu, 23 May 2019 14:15:19 -0400 Subject: fix assert_string_list docstring value=None raises TypeError DistutilsSetupError: 2 must be a list of strings (got None) --- setuptools/dist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/dist.py b/setuptools/dist.py index ea6411b1..1e1b83be 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -213,7 +213,7 @@ def check_importable(dist, attr, value): def assert_string_list(dist, attr, value): - """Verify that value is a string list or None""" + """Verify that value is a string list""" try: assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError): -- cgit v1.2.3 From 8f848bd777278fc8dcb42dc45751cd8b95ec2a02 Mon Sep 17 00:00:00 2001 From: Daniel Himmelstein Date: Wed, 22 May 2019 17:45:44 -0400 Subject: improve `package_data` check Ensure the dictionary values are lists/tuples of strings. Fix #1459. --- changelog.d/1769.change.rst | 1 + setuptools/dist.py | 29 +++++++++++------------ setuptools/tests/test_dist.py | 53 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 changelog.d/1769.change.rst diff --git a/changelog.d/1769.change.rst b/changelog.d/1769.change.rst new file mode 100644 index 00000000..d48a23b6 --- /dev/null +++ b/changelog.d/1769.change.rst @@ -0,0 +1 @@ +Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings. diff --git a/setuptools/dist.py b/setuptools/dist.py index 1e1b83be..f0f030b5 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -215,6 +215,10 @@ def check_importable(dist, attr, value): def assert_string_list(dist, attr, value): """Verify that value is a string list""" try: + # verify that value is a list or tuple to exclude unordered + # or single-use iterables + assert isinstance(value, (list, tuple)) + # verify that elements of value are strings assert ''.join(value) != value except (TypeError, ValueError, AttributeError, AssertionError): raise DistutilsSetupError( @@ -307,20 +311,17 @@ def check_test_suite(dist, attr, value): def check_package_data(dist, attr, value): """Verify that value is a dictionary of package names to glob lists""" - if isinstance(value, dict): - for k, v in value.items(): - if not isinstance(k, str): - break - try: - iter(v) - except TypeError: - break - else: - return - raise DistutilsSetupError( - attr + " must be a dictionary mapping package names to lists of " - "wildcard patterns" - ) + if not isinstance(value, dict): + raise DistutilsSetupError( + "{!r} must be a dictionary mapping package names to lists of " + "string wildcard patterns".format(attr)) + for k, v in value.items(): + if not isinstance(k, six.string_types): + raise DistutilsSetupError( + "keys of {!r} dict must be strings (got {!r})" + .format(attr, k) + ) + assert_string_list(dist, 'values of {!r} dict'.format(attr), v) def check_packages(dist, attr, value): diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 390c3dfc..c771a19a 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -3,7 +3,13 @@ from __future__ import unicode_literals import io -from setuptools.dist import DistDeprecationWarning, _get_unpatched +import re +from distutils.errors import DistutilsSetupError +from setuptools.dist import ( + _get_unpatched, + check_package_data, + DistDeprecationWarning, +) from setuptools import Distribution from setuptools.extern.six.moves.urllib.request import pathname2url from setuptools.extern.six.moves.urllib_parse import urljoin @@ -263,3 +269,48 @@ def test_maintainer_author(name, attrs, tmpdir): else: line = '%s: %s' % (fkey, val) assert line in pkg_lines_set + + +CHECK_PACKAGE_DATA_TESTS = ( + # Valid. + ({ + '': ['*.txt', '*.rst'], + 'hello': ['*.msg'], + }, None), + # Not a dictionary. + (( + ('', ['*.txt', '*.rst']), + ('hello', ['*.msg']), + ), ( + "'package_data' must be a dictionary mapping package" + " names to lists of string wildcard patterns" + )), + # Invalid key type. + ({ + 400: ['*.txt', '*.rst'], + }, ( + "keys of 'package_data' dict must be strings (got 400)" + )), + # Invalid value type. + ({ + 'hello': str('*.msg'), + }, ( + "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')" + )), + # Invalid value type (generators are single use) + ({ + 'hello': (x for x in "generator"), + }, ( + "\"values of 'package_data' dict\" must be a list of strings " + "(got Date: Tue, 23 Jul 2019 11:29:21 +0200 Subject: tests: fix `test_pip_upgrade_from_source` on Python 3.4 Do not test pip's master on 3.4, as support for it has been dropped. --- setuptools/tests/test_virtualenv.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index d7b98c77..74a1284c 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -8,6 +8,8 @@ from pytest_fixture_config import yield_requires_config import pytest_virtualenv +from setuptools.extern import six + from .textwrap import DALS from .test_easy_install import make_nspkg_sdist @@ -75,9 +77,12 @@ def _get_pip_versions(): 'pip==10.0.1', 'pip==18.1', 'pip==19.0.1', - 'https://github.com/pypa/pip/archive/master.zip', ] + # Pip's master dropped support for 3.4. + if not six.PY34: + network_versions.append('https://github.com/pypa/pip/archive/master.zip') + versions = [None] + [ pytest.param(v, **({} if network else {'marks': pytest.mark.skip})) for v in network_versions -- cgit v1.2.3 From 7e1b1934c7e0dcd400ff17a701601d912aa603bc Mon Sep 17 00:00:00 2001 From: jgoutin Date: Sat, 3 Aug 2019 10:41:34 +0200 Subject: Improve Visual C++ 14.X support Improve VC++14 support for VS 2017 and 2019. Separate VC from VS version (Miss match starting VS15). Improve docstrings args and returns information + fixe typos. Fix coding style and minor coding issues. Remove Microsoft "Windows SDK 7.0" dead link. --- changelog.d/1811.change.rst | 1 + setuptools/msvc.py | 1021 +++++++++++++++++++++++++++++-------------- 2 files changed, 700 insertions(+), 322 deletions(-) create mode 100644 changelog.d/1811.change.rst diff --git a/changelog.d/1811.change.rst b/changelog.d/1811.change.rst new file mode 100644 index 00000000..dc52c6db --- /dev/null +++ b/changelog.d/1811.change.rst @@ -0,0 +1 @@ +Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019. \ No newline at end of file diff --git a/setuptools/msvc.py b/setuptools/msvc.py index b9c472f1..ffa7053b 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -11,13 +11,17 @@ Microsoft Visual C++ 9.0: Microsoft Visual C++ 10.0: Microsoft Windows SDK 7.1 (x86, x64, ia64) -Microsoft Visual C++ 14.0: +Microsoft Visual C++ 14.X: Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) - Microsoft Visual Studio 2017 (x86, x64, arm, arm64) Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) + Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64) + +This may also support compilers shipped with compatible Visual Studio versions. """ -import os +import json +from os import listdir, pathsep +from os.path import join, isfile, isdir, dirname import sys import platform import itertools @@ -30,12 +34,9 @@ from .monkey import get_unpatched if platform.system() == 'Windows': from setuptools.extern.six.moves import winreg - safe_env = os.environ + from os import environ else: - """ - Mock winreg and environ so the module can be imported - on this platform. - """ + # Mock winreg and environ so the module can be imported on this platform. class winreg: HKEY_USERS = None @@ -43,7 +44,7 @@ else: HKEY_LOCAL_MACHINE = None HKEY_CLASSES_ROOT = None - safe_env = dict() + environ = dict() _msvc9_suppress_errors = ( # msvc9compiler isn't available on some platforms @@ -63,15 +64,13 @@ except _msvc9_suppress_errors: def msvc9_find_vcvarsall(version): """ Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone - compiler build for Python (VCForPython). Fall back to original behavior - when the standalone compiler is not available. + compiler build for Python + (VCForPython / Microsoft Visual C++ Compiler for Python 2.7). - Redirect the path of "vcvarsall.bat". + Fall back to original behavior when the standalone compiler is not + available. - Known supported compilers - ------------------------- - Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) + Redirect the path of "vcvarsall.bat". Parameters ---------- @@ -80,24 +79,25 @@ def msvc9_find_vcvarsall(version): Return ------ - vcvarsall.bat path: str + str + vcvarsall.bat path """ - VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' - key = VC_BASE % ('', version) + vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' + key = vc_base % ('', version) try: # Per-user installs register the compiler path here productdir = Reg.get_value(key, "installdir") except KeyError: try: # All-user installs on a 64-bit system register here - key = VC_BASE % ('Wow6432Node\\', version) + key = vc_base % ('Wow6432Node\\', version) productdir = Reg.get_value(key, "installdir") except KeyError: productdir = None if productdir: - vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat") - if os.path.isfile(vcvarsall): + vcvarsall = join(productdir, "vcvarsall.bat") + if isfile(vcvarsall): return vcvarsall return get_unpatched(msvc9_find_vcvarsall)(version) @@ -106,20 +106,10 @@ def msvc9_find_vcvarsall(version): def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): """ Patched "distutils.msvc9compiler.query_vcvarsall" for support extra - compilers. + Microsoft Visual C++ 9.0 and 10.0 compilers. Set environment without use of "vcvarsall.bat". - Known supported compilers - ------------------------- - Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) - Microsoft Windows SDK 6.1 (x86, x64, ia64) - Microsoft Windows SDK 7.0 (x86, x64, ia64) - - Microsoft Visual C++ 10.0: - Microsoft Windows SDK 7.1 (x86, x64, ia64) - Parameters ---------- ver: float @@ -129,9 +119,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): Return ------ - environment: dict + dict + environment """ - # Try to get environement from vcvarsall.bat (Classical way) + # Try to get environment from vcvarsall.bat (Classical way) try: orig = get_unpatched(msvc9_query_vcvarsall) return orig(ver, arch, *args, **kwargs) @@ -153,17 +144,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): def msvc14_get_vc_env(plat_spec): """ Patched "distutils._msvccompiler._get_vc_env" for support extra - compilers. + Microsoft Visual C++ 14.X compilers. Set environment without use of "vcvarsall.bat". - Known supported compilers - ------------------------- - Microsoft Visual C++ 14.0: - Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) - Microsoft Visual Studio 2017 (x86, x64, arm, arm64) - Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) - Parameters ---------- plat_spec: str @@ -171,7 +155,8 @@ def msvc14_get_vc_env(plat_spec): Return ------ - environment: dict + dict + environment """ # Try to get environment from vcvarsall.bat (Classical way) try: @@ -217,9 +202,9 @@ def _augment_exception(exc, version, arch=''): if version == 9.0: if arch.lower().find('ia64') > -1: # For VC++ 9.0, if IA64 support is needed, redirect user - # to Windows SDK 7.0 - message += ' Get it with "Microsoft Windows SDK 7.0": ' - message += msdownload % 3138 + # to Windows SDK 7.0. + # Note: No download link available from Microsoft. + message += ' Get it with "Microsoft Windows SDK 7.0"' else: # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : # This redirection link is maintained by Microsoft. @@ -230,8 +215,8 @@ def _augment_exception(exc, version, arch=''): message += ' Get it with "Microsoft Windows SDK 7.1": ' message += msdownload % 8279 elif version >= 14.0: - # For VC++ 14.0 Redirect user to Visual C++ Build Tools - message += (' Get it with "Microsoft Visual C++ Build Tools": ' + # For VC++ 14.X Redirect user to latest Visual C++ Build Tools + message += (' Get it with "Build Tools for Visual Studio": ' r'https://visualstudio.microsoft.com/downloads/') exc.args = (message, ) @@ -239,26 +224,50 @@ def _augment_exception(exc, version, arch=''): class PlatformInfo: """ - Current and Target Architectures informations. + Current and Target Architectures information. Parameters ---------- arch: str Target architecture. """ - current_cpu = safe_env.get('processor_architecture', '').lower() + current_cpu = environ.get('processor_architecture', '').lower() def __init__(self, arch): self.arch = arch.lower().replace('x64', 'amd64') @property def target_cpu(self): + """ + Return Target CPU architecture. + + Return + ------ + str + Target CPU + """ return self.arch[self.arch.find('_') + 1:] def target_is_x86(self): + """ + Return True if target CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ return self.target_cpu == 'x86' def current_is_x86(self): + """ + Return True if current CPU is x86 32 bits.. + + Return + ------ + bool + CPU is x86 32 bits + """ return self.current_cpu == 'x86' def current_dir(self, hidex86=False, x64=False): @@ -274,8 +283,8 @@ class PlatformInfo: Return ------ - subfolder: str - '\target', or '' (see hidex86 parameter) + str + subfolder: '\target', or '' (see hidex86 parameter) """ return ( '' if (self.current_cpu == 'x86' and hidex86) else @@ -296,8 +305,8 @@ class PlatformInfo: Return ------ - subfolder: str - '\current', or '' (see hidex86 parameter) + str + subfolder: '\current', or '' (see hidex86 parameter) """ return ( '' if (self.target_cpu == 'x86' and hidex86) else @@ -312,13 +321,13 @@ class PlatformInfo: Parameters ---------- forcex86: bool - Use 'x86' as current architecture even if current acritecture is + Use 'x86' as current architecture even if current architecture is not x86. Return ------ - subfolder: str - '' if target architecture is current architecture, + str + subfolder: '' if target architecture is current architecture, '\current_target' if not. """ current = 'x86' if forcex86 else self.current_cpu @@ -330,7 +339,7 @@ class PlatformInfo: class RegistryInfo: """ - Microsoft Visual Studio related registry informations. + Microsoft Visual Studio related registry information. Parameters ---------- @@ -349,6 +358,11 @@ class RegistryInfo: def visualstudio(self): """ Microsoft Visual Studio root registry key. + + Return + ------ + str + Registry key """ return 'VisualStudio' @@ -356,27 +370,47 @@ class RegistryInfo: def sxs(self): """ Microsoft Visual Studio SxS registry key. + + Return + ------ + str + Registry key """ - return os.path.join(self.visualstudio, 'SxS') + return join(self.visualstudio, 'SxS') @property def vc(self): """ Microsoft Visual C++ VC7 registry key. + + Return + ------ + str + Registry key """ - return os.path.join(self.sxs, 'VC7') + return join(self.sxs, 'VC7') @property def vs(self): """ Microsoft Visual Studio VS7 registry key. + + Return + ------ + str + Registry key """ - return os.path.join(self.sxs, 'VS7') + return join(self.sxs, 'VS7') @property def vc_for_python(self): """ Microsoft Visual C++ for Python registry key. + + Return + ------ + str + Registry key """ return r'DevDiv\VCForPython' @@ -384,6 +418,11 @@ class RegistryInfo: def microsoft_sdk(self): """ Microsoft SDK registry key. + + Return + ------ + str + Registry key """ return 'Microsoft SDKs' @@ -391,20 +430,35 @@ class RegistryInfo: def windows_sdk(self): """ Microsoft Windows/Platform SDK registry key. + + Return + ------ + str + Registry key """ - return os.path.join(self.microsoft_sdk, 'Windows') + return join(self.microsoft_sdk, 'Windows') @property def netfx_sdk(self): """ Microsoft .NET Framework SDK registry key. + + Return + ------ + str + Registry key """ - return os.path.join(self.microsoft_sdk, 'NETFXSDK') + return join(self.microsoft_sdk, 'NETFXSDK') @property def windows_kits_roots(self): """ Microsoft Windows Kits Roots registry key. + + Return + ------ + str + Registry key """ return r'Windows Kits\Installed Roots' @@ -421,10 +475,11 @@ class RegistryInfo: Return ------ - str: value + str + Registry key """ node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' - return os.path.join('Software', node64, 'Microsoft', key) + return join('Software', node64, 'Microsoft', key) def lookup(self, key, name): """ @@ -439,18 +494,19 @@ class RegistryInfo: Return ------ - str: value + str + value """ - KEY_READ = winreg.KEY_READ + key_read = winreg.KEY_READ openkey = winreg.OpenKey ms = self.microsoft for hkey in self.HKEYS: try: - bkey = openkey(hkey, ms(key), 0, KEY_READ) + bkey = openkey(hkey, ms(key), 0, key_read) except (OSError, IOError): if not self.pi.current_is_x86(): try: - bkey = openkey(hkey, ms(key, True), 0, KEY_READ) + bkey = openkey(hkey, ms(key, True), 0, key_read) except (OSError, IOError): continue else: @@ -463,7 +519,7 @@ class RegistryInfo: class SystemInfo: """ - Microsoft Windows and Visual Studio related system inormations. + Microsoft Windows and Visual Studio related system information. Parameters ---------- @@ -474,30 +530,52 @@ class SystemInfo: """ # Variables and properties in this class use originals CamelCase variables - # names from Microsoft source files for more easy comparaison. - WinDir = safe_env.get('WinDir', '') - ProgramFiles = safe_env.get('ProgramFiles', '') - ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles) + # names from Microsoft source files for more easy comparison. + WinDir = environ.get('WinDir', '') + ProgramFiles = environ.get('ProgramFiles', '') + ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles) def __init__(self, registry_info, vc_ver=None): self.ri = registry_info self.pi = self.ri.pi - self.vc_ver = vc_ver or self._find_latest_available_vc_ver() - def _find_latest_available_vc_ver(self): - try: - return self.find_available_vc_vers()[-1] - except IndexError: - err = 'No Microsoft Visual C++ version found' - raise distutils.errors.DistutilsPlatformError(err) + self.known_vs_paths = self.find_programdata_vs_vers() + + # Except for VS15+, VC version is aligned with VS version + self.vs_ver = self.vc_ver = ( + vc_ver or self._find_latest_available_vs_ver()) + + def _find_latest_available_vs_ver(self): + """ + Find the latest VC version + + Return + ------ + float + version + """ + reg_vc_vers = self.find_reg_vs_vers() + + if not (reg_vc_vers or self.known_vs_paths): + raise distutils.errors.DistutilsPlatformError( + 'No Microsoft Visual C++ version found') + + vc_vers = set(reg_vc_vers) + vc_vers.update(self.known_vs_paths) + return sorted(vc_vers)[-1] - def find_available_vc_vers(self): + def find_reg_vs_vers(self): """ - Find all available Microsoft Visual C++ versions. + Find Microsoft Visual Studio versions available in registry. + + Return + ------ + list of float + Versions """ ms = self.ri.microsoft vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) - vc_vers = [] + vs_vers = [] for hkey in self.ri.HKEYS: for key in vckeys: try: @@ -508,49 +586,108 @@ class SystemInfo: for i in range(values): try: ver = float(winreg.EnumValue(bkey, i)[0]) - if ver not in vc_vers: - vc_vers.append(ver) + if ver not in vs_vers: + vs_vers.append(ver) except ValueError: pass for i in range(subkeys): try: ver = float(winreg.EnumKey(bkey, i)) - if ver not in vc_vers: - vc_vers.append(ver) + if ver not in vs_vers: + vs_vers.append(ver) except ValueError: pass - return sorted(vc_vers) + return sorted(vs_vers) + + def find_programdata_vs_vers(self): + r""" + Find Visual studio 2017+ versions from information in + "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances". + + Return + ------ + dict + float version as key, path as value. + """ + vs_versions = {} + instances_dir = \ + r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances' + + try: + hashed_names = listdir(instances_dir) + + except (OSError, IOError): + # Directory not exists with all Visual Studio versions + return vs_versions + + for name in hashed_names: + try: + # Get VS installation path from "state.json" file + state_path = join(instances_dir, name, 'state.json') + with open(state_path, 'rt', encoding='utf-8') as state_file: + state = json.load(state_file) + vs_path = state['installationPath'] + + # Raises OSError if this VS installation does not contain VC + listdir(join(vs_path, r'VC\Tools\MSVC')) + + # Store version and path + vs_versions[self._as_float_version( + state['installationVersion'])] = vs_path + + except (OSError, IOError, KeyError): + # Skip if "state.json" file is missing or bad format + continue + + return vs_versions + + @staticmethod + def _as_float_version(version): + """ + Return a string version as a simplified float version (major.minor) + + Parameters + ---------- + version: str + Version. + + Return + ------ + float + version + """ + return float('.'.join(version.split('.')[:2])) @property def VSInstallDir(self): """ Microsoft Visual Studio directory. + + Return + ------ + str + path """ # Default path - name = 'Microsoft Visual Studio %0.1f' % self.vc_ver - default = os.path.join(self.ProgramFilesx86, name) + default = join(self.ProgramFilesx86, + 'Microsoft Visual Studio %0.1f' % self.vs_ver) # Try to get path from registry, if fail use default path - return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default + return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default @property def VCInstallDir(self): """ Microsoft Visual C++ directory. - """ - self.VSInstallDir - - guess_vc = self._guess_vc() or self._guess_vc_legacy() - - # Try to get "VC++ for Python" path from registry as default path - reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) - python_vc = self.ri.lookup(reg_path, 'installdir') - default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc - # Try to get path from registry, if fail use default path - path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc + Return + ------ + str + path + """ + path = self._guess_vc() or self._guess_vc_legacy() - if not os.path.isdir(path): + if not isdir(path): msg = 'Microsoft Visual C++ directory not found' raise distutils.errors.DistutilsPlatformError(msg) @@ -558,186 +695,256 @@ class SystemInfo: def _guess_vc(self): """ - Locate Visual C for 2017 + Locate Visual C++ for VS2017+. + + Return + ------ + str + path """ - if self.vc_ver <= 14.0: - return + if self.vs_ver <= 14.0: + return '' + + try: + # First search in known VS paths + vs_dir = self.known_vs_paths[self.vs_ver] + except KeyError: + # Else, search with path from registry + vs_dir = self.VSInstallDir + + guess_vc = join(vs_dir, r'VC\Tools\MSVC') - default = r'VC\Tools\MSVC' - guess_vc = os.path.join(self.VSInstallDir, default) # Subdir with VC exact version as name try: - vc_exact_ver = os.listdir(guess_vc)[-1] - return os.path.join(guess_vc, vc_exact_ver) + # Update the VC version with real one instead of VS version + vc_ver = listdir(guess_vc)[-1] + self.vc_ver = self._as_float_version(vc_ver) + return join(guess_vc, vc_ver) except (OSError, IOError, IndexError): - pass + return '' def _guess_vc_legacy(self): """ - Locate Visual C for versions prior to 2017 + Locate Visual C++ for versions prior to 2017. + + Return + ------ + str + path """ - default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver - return os.path.join(self.ProgramFilesx86, default) + default = join(self.ProgramFilesx86, + r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver) + + # Try to get "VC++ for Python" path from registry as default path + reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver) + python_vc = self.ri.lookup(reg_path, 'installdir') + default_vc = join(python_vc, 'VC') if python_vc else default + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc @property def WindowsSdkVersion(self): """ Microsoft Windows SDK versions for specified MSVC++ version. - """ - if self.vc_ver <= 9.0: - return ('7.0', '6.1', '6.0a') - elif self.vc_ver == 10.0: - return ('7.1', '7.0a') - elif self.vc_ver == 11.0: - return ('8.0', '8.0a') - elif self.vc_ver == 12.0: - return ('8.1', '8.1a') - elif self.vc_ver >= 14.0: - return ('10.0', '8.1') + + Return + ------ + tuple of str + versions + """ + if self.vs_ver <= 9.0: + return '7.0', '6.1', '6.0a' + elif self.vs_ver == 10.0: + return '7.1', '7.0a' + elif self.vs_ver == 11.0: + return '8.0', '8.0a' + elif self.vs_ver == 12.0: + return '8.1', '8.1a' + elif self.vs_ver >= 14.0: + return '10.0', '8.1' @property def WindowsSdkLastVersion(self): """ - Microsoft Windows SDK last version + Microsoft Windows SDK last version. + + Return + ------ + str + version """ - return self._use_last_dir_name(os.path.join( - self.WindowsSdkDir, 'lib')) + return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib')) @property def WindowsSdkDir(self): """ Microsoft Windows SDK directory. + + Return + ------ + str + path """ sdkdir = '' for ver in self.WindowsSdkVersion: # Try to get it from registry - loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver) + loc = join(self.ri.windows_sdk, 'v%s' % ver) sdkdir = self.ri.lookup(loc, 'installationfolder') if sdkdir: break - if not sdkdir or not os.path.isdir(sdkdir): + if not sdkdir or not isdir(sdkdir): # Try to get "VC++ for Python" version from registry - path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) + path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) install_base = self.ri.lookup(path, 'installdir') if install_base: - sdkdir = os.path.join(install_base, 'WinSDK') - if not sdkdir or not os.path.isdir(sdkdir): + sdkdir = join(install_base, 'WinSDK') + if not sdkdir or not isdir(sdkdir): # If fail, use default new path for ver in self.WindowsSdkVersion: intver = ver[:ver.rfind('.')] - path = r'Microsoft SDKs\Windows Kits\%s' % (intver) - d = os.path.join(self.ProgramFiles, path) - if os.path.isdir(d): + path = r'Microsoft SDKs\Windows Kits\%s' % intver + d = join(self.ProgramFiles, path) + if isdir(d): sdkdir = d - if not sdkdir or not os.path.isdir(sdkdir): + if not sdkdir or not isdir(sdkdir): # If fail, use default old path for ver in self.WindowsSdkVersion: path = r'Microsoft SDKs\Windows\v%s' % ver - d = os.path.join(self.ProgramFiles, path) - if os.path.isdir(d): + d = join(self.ProgramFiles, path) + if isdir(d): sdkdir = d if not sdkdir: # If fail, use Platform SDK - sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK') + sdkdir = join(self.VCInstallDir, 'PlatformSDK') return sdkdir @property def WindowsSDKExecutablePath(self): """ Microsoft Windows SDK executable directory. + + Return + ------ + str + path """ # Find WinSDK NetFx Tools registry dir name - if self.vc_ver <= 11.0: + if self.vs_ver <= 11.0: netfxver = 35 arch = '' else: netfxver = 40 - hidex86 = True if self.vc_ver <= 12.0 else False + hidex86 = True if self.vs_ver <= 12.0 else False arch = self.pi.current_dir(x64=True, hidex86=hidex86) fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) - # liste all possibles registry paths + # list all possibles registry paths regpaths = [] - if self.vc_ver >= 14.0: + if self.vs_ver >= 14.0: for ver in self.NetFxSdkVersion: - regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)] + regpaths += [join(self.ri.netfx_sdk, ver, fx)] for ver in self.WindowsSdkVersion: - regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)] + regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)] # Return installation folder from the more recent path for path in regpaths: execpath = self.ri.lookup(path, 'installationfolder') if execpath: - break - return execpath + return execpath @property def FSharpInstallDir(self): """ Microsoft Visual F# directory. + + Return + ------ + str + path """ - path = r'%0.1f\Setup\F#' % self.vc_ver - path = os.path.join(self.ri.visualstudio, path) + path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver) return self.ri.lookup(path, 'productdir') or '' @property def UniversalCRTSdkDir(self): """ Microsoft Universal CRT SDK directory. + + Return + ------ + str + path """ # Set Kit Roots versions for specified MSVC++ version - if self.vc_ver >= 14.0: - vers = ('10', '81') - else: - vers = () + vers = ('10', '81') if self.vs_ver >= 14.0 else () # Find path of the more recent Kit for ver in vers: sdkdir = self.ri.lookup(self.ri.windows_kits_roots, 'kitsroot%s' % ver) if sdkdir: - break - return sdkdir or '' + return sdkdir or '' @property def UniversalCRTSdkLastVersion(self): """ - Microsoft Universal C Runtime SDK last version + Microsoft Universal C Runtime SDK last version. + + Return + ------ + str + version """ - return self._use_last_dir_name(os.path.join( - self.UniversalCRTSdkDir, 'lib')) + return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib')) @property def NetFxSdkVersion(self): """ Microsoft .NET Framework SDK versions. + + Return + ------ + tuple of str + versions """ - # Set FxSdk versions for specified MSVC++ version - if self.vc_ver >= 14.0: - return ('4.6.1', '4.6') - else: - return () + # Set FxSdk versions for specified VS version + return (('4.7.2', '4.7.1', '4.7', + '4.6.2', '4.6.1', '4.6', + '4.5.2', '4.5.1', '4.5') + if self.vs_ver >= 14.0 else ()) @property def NetFxSdkDir(self): """ Microsoft .NET Framework SDK directory. + + Return + ------ + str + path """ + sdkdir = '' for ver in self.NetFxSdkVersion: - loc = os.path.join(self.ri.netfx_sdk, ver) + loc = join(self.ri.netfx_sdk, ver) sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') if sdkdir: break - return sdkdir or '' + return sdkdir @property def FrameworkDir32(self): """ Microsoft .NET Framework 32bit directory. + + Return + ------ + str + path """ # Default path - guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework') + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework') # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw @@ -746,9 +953,14 @@ class SystemInfo: def FrameworkDir64(self): """ Microsoft .NET Framework 64bit directory. + + Return + ------ + str + path """ # Default path - guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64') + guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64') # Try to get path from registry, if fail use default path return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw @@ -757,6 +969,11 @@ class SystemInfo: def FrameworkVersion32(self): """ Microsoft .NET Framework 32bit versions. + + Return + ------ + tuple of str + versions """ return self._find_dot_net_versions(32) @@ -764,6 +981,11 @@ class SystemInfo: def FrameworkVersion64(self): """ Microsoft .NET Framework 64bit versions. + + Return + ------ + tuple of str + versions """ return self._find_dot_net_versions(64) @@ -775,6 +997,11 @@ class SystemInfo: ---------- bits: int Platform number of bits: 32 or 64. + + Return + ------ + tuple of str + versions """ # Find actual .NET version in registry reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) @@ -782,18 +1009,17 @@ class SystemInfo: ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' # Set .NET versions for specified MSVC++ version - if self.vc_ver >= 12.0: - frameworkver = (ver, 'v4.0') - elif self.vc_ver >= 10.0: - frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver, - 'v3.5') - elif self.vc_ver == 9.0: - frameworkver = ('v3.5', 'v2.0.50727') - if self.vc_ver == 8.0: - frameworkver = ('v3.0', 'v2.0.50727') - return frameworkver - - def _use_last_dir_name(self, path, prefix=''): + if self.vs_ver >= 12.0: + return ver, 'v4.0' + elif self.vs_ver >= 10.0: + return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5' + elif self.vs_ver == 9.0: + return 'v3.5', 'v2.0.50727' + elif self.vs_ver == 8.0: + return 'v3.0', 'v2.0.50727' + + @staticmethod + def _use_last_dir_name(path, prefix=''): """ Return name of the last dir in path or '' if no dir found. @@ -802,12 +1028,17 @@ class SystemInfo: path: str Use dirs in this path prefix: str - Use only dirs startings by this prefix + Use only dirs starting by this prefix + + Return + ------ + str + name """ matching_dirs = ( dir_name - for dir_name in reversed(os.listdir(path)) - if os.path.isdir(os.path.join(path, dir_name)) and + for dir_name in reversed(listdir(path)) + if isdir(join(path, dir_name)) and dir_name.startswith(prefix) ) return next(matching_dirs, None) or '' @@ -818,7 +1049,7 @@ class EnvironmentInfo: Return environment variables for specified Microsoft Visual C++ version and platform : Lib, Include, Path and libpath. - This function is compatible with Microsoft Visual C++ 9.0 to 14.0. + This function is compatible with Microsoft Visual C++ 9.0 to 14.X. Script created by analysing Microsoft environment configuration files like "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... @@ -835,7 +1066,7 @@ class EnvironmentInfo: """ # Variables and properties in this class use originals CamelCase variables - # names from Microsoft source files for more easy comparaison. + # names from Microsoft source files for more easy comparison. def __init__(self, arch, vc_ver=None, vc_min_ver=0): self.pi = PlatformInfo(arch) @@ -846,205 +1077,255 @@ class EnvironmentInfo: err = 'No suitable Microsoft Visual C++ version found' raise distutils.errors.DistutilsPlatformError(err) + @property + def vs_ver(self): + """ + Microsoft Visual Studio. + + Return + ------ + float + version + """ + return self.si.vs_ver + @property def vc_ver(self): """ Microsoft Visual C++ version. + + Return + ------ + float + version """ return self.si.vc_ver @property def VSTools(self): """ - Microsoft Visual Studio Tools + Microsoft Visual Studio Tools. + + Return + ------ + list of str + paths """ paths = [r'Common7\IDE', r'Common7\Tools'] - if self.vc_ver >= 14.0: + if self.vs_ver >= 14.0: arch_subdir = self.pi.current_dir(hidex86=True, x64=True) paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] paths += [r'Team Tools\Performance Tools'] paths += [r'Team Tools\Performance Tools%s' % arch_subdir] - return [os.path.join(self.si.VSInstallDir, path) for path in paths] + return [join(self.si.VSInstallDir, path) for path in paths] @property def VCIncludes(self): """ - Microsoft Visual C++ & Microsoft Foundation Class Includes + Microsoft Visual C++ & Microsoft Foundation Class Includes. + + Return + ------ + list of str + paths """ - return [os.path.join(self.si.VCInstallDir, 'Include'), - os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')] + return [join(self.si.VCInstallDir, 'Include'), + join(self.si.VCInstallDir, r'ATLMFC\Include')] @property def VCLibraries(self): """ - Microsoft Visual C++ & Microsoft Foundation Class Libraries + Microsoft Visual C++ & Microsoft Foundation Class Libraries. + + Return + ------ + list of str + paths """ - if self.vc_ver >= 15.0: + if self.vs_ver >= 15.0: arch_subdir = self.pi.target_dir(x64=True) else: arch_subdir = self.pi.target_dir(hidex86=True) paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] - if self.vc_ver >= 14.0: + if self.vs_ver >= 14.0: paths += [r'Lib\store%s' % arch_subdir] - return [os.path.join(self.si.VCInstallDir, path) for path in paths] + return [join(self.si.VCInstallDir, path) for path in paths] @property def VCStoreRefs(self): """ - Microsoft Visual C++ store references Libraries + Microsoft Visual C++ store references Libraries. + + Return + ------ + list of str + paths """ - if self.vc_ver < 14.0: + if self.vs_ver < 14.0: return [] - return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')] + return [join(self.si.VCInstallDir, r'Lib\store\references')] @property def VCTools(self): """ - Microsoft Visual C++ Tools + Microsoft Visual C++ Tools. + + Return + ------ + list of str + paths """ si = self.si - tools = [os.path.join(si.VCInstallDir, 'VCPackages')] + tools = [join(si.VCInstallDir, 'VCPackages')] - forcex86 = True if self.vc_ver <= 10.0 else False + forcex86 = True if self.vs_ver <= 10.0 else False arch_subdir = self.pi.cross_dir(forcex86) if arch_subdir: - tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)] + tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)] - if self.vc_ver == 14.0: + if self.vs_ver == 14.0: path = 'Bin%s' % self.pi.current_dir(hidex86=True) - tools += [os.path.join(si.VCInstallDir, path)] + tools += [join(si.VCInstallDir, path)] - elif self.vc_ver >= 15.0: + elif self.vs_ver >= 15.0: host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else r'bin\HostX64%s') - tools += [os.path.join( + tools += [join( si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] if self.pi.current_cpu != self.pi.target_cpu: - tools += [os.path.join( + tools += [join( si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] else: - tools += [os.path.join(si.VCInstallDir, 'Bin')] + tools += [join(si.VCInstallDir, 'Bin')] return tools @property def OSLibraries(self): """ - Microsoft Windows SDK Libraries + Microsoft Windows SDK Libraries. + + Return + ------ + list of str + paths """ - if self.vc_ver <= 10.0: + if self.vs_ver <= 10.0: arch_subdir = self.pi.target_dir(hidex86=True, x64=True) - return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] + return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] else: arch_subdir = self.pi.target_dir(x64=True) - lib = os.path.join(self.si.WindowsSdkDir, 'lib') + lib = join(self.si.WindowsSdkDir, 'lib') libver = self._sdk_subdir - return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))] + return [join(lib, '%sum%s' % (libver , arch_subdir))] @property def OSIncludes(self): """ - Microsoft Windows SDK Include + Microsoft Windows SDK Include. + + Return + ------ + list of str + paths """ - include = os.path.join(self.si.WindowsSdkDir, 'include') + include = join(self.si.WindowsSdkDir, 'include') - if self.vc_ver <= 10.0: - return [include, os.path.join(include, 'gl')] + if self.vs_ver <= 10.0: + return [include, join(include, 'gl')] else: - if self.vc_ver >= 14.0: + if self.vs_ver >= 14.0: sdkver = self._sdk_subdir else: sdkver = '' - return [os.path.join(include, '%sshared' % sdkver), - os.path.join(include, '%sum' % sdkver), - os.path.join(include, '%swinrt' % sdkver)] + return [join(include, '%sshared' % sdkver), + join(include, '%sum' % sdkver), + join(include, '%swinrt' % sdkver)] @property def OSLibpath(self): """ - Microsoft Windows SDK Libraries Paths + Microsoft Windows SDK Libraries Paths. + + Return + ------ + list of str + paths """ - ref = os.path.join(self.si.WindowsSdkDir, 'References') + ref = join(self.si.WindowsSdkDir, 'References') libpath = [] - if self.vc_ver <= 9.0: + if self.vs_ver <= 9.0: libpath += self.OSLibraries - if self.vc_ver >= 11.0: - libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')] + if self.vs_ver >= 11.0: + libpath += [join(ref, r'CommonConfiguration\Neutral')] - if self.vc_ver >= 14.0: + if self.vs_ver >= 14.0: libpath += [ ref, - os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'), - os.path.join( - ref, - 'Windows.Foundation.UniversalApiContract', - '1.0.0.0', - ), - os.path.join( - ref, - 'Windows.Foundation.FoundationContract', - '1.0.0.0', - ), - os.path.join( - ref, - 'Windows.Networking.Connectivity.WwanContract', - '1.0.0.0', - ), - os.path.join( - self.si.WindowsSdkDir, - 'ExtensionSDKs', - 'Microsoft.VCLibs', - '%0.1f' % self.vc_ver, - 'References', - 'CommonConfiguration', - 'neutral', - ), + join(self.si.WindowsSdkDir, 'UnionMetadata'), + join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'), + join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'), + join(ref,'Windows.Networking.Connectivity.WwanContract', + '1.0.0.0'), + join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', + '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', + 'neutral'), ] return libpath @property def SdkTools(self): """ - Microsoft Windows SDK Tools + Microsoft Windows SDK Tools. + + Return + ------ + list of str + paths """ return list(self._sdk_tools()) def _sdk_tools(self): """ - Microsoft Windows SDK Tools paths generator + Microsoft Windows SDK Tools paths generator. + + Return + ------ + generator of str + paths """ - if self.vc_ver < 15.0: - bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86' - yield os.path.join(self.si.WindowsSdkDir, bin_dir) + if self.vs_ver < 15.0: + bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86' + yield join(self.si.WindowsSdkDir, bin_dir) if not self.pi.current_is_x86(): arch_subdir = self.pi.current_dir(x64=True) path = 'Bin%s' % arch_subdir - yield os.path.join(self.si.WindowsSdkDir, path) + yield join(self.si.WindowsSdkDir, path) - if self.vc_ver == 10.0 or self.vc_ver == 11.0: + if self.vs_ver in (10.0, 11.0): if self.pi.target_is_x86(): arch_subdir = '' else: arch_subdir = self.pi.current_dir(hidex86=True, x64=True) path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir - yield os.path.join(self.si.WindowsSdkDir, path) + yield join(self.si.WindowsSdkDir, path) - elif self.vc_ver >= 15.0: - path = os.path.join(self.si.WindowsSdkDir, 'Bin') + elif self.vs_ver >= 15.0: + path = join(self.si.WindowsSdkDir, 'Bin') arch_subdir = self.pi.current_dir(x64=True) sdkver = self.si.WindowsSdkLastVersion - yield os.path.join(path, '%s%s' % (sdkver, arch_subdir)) + yield join(path, '%s%s' % (sdkver, arch_subdir)) if self.si.WindowsSDKExecutablePath: yield self.si.WindowsSDKExecutablePath @@ -1052,7 +1333,12 @@ class EnvironmentInfo: @property def _sdk_subdir(self): """ - Microsoft Windows SDK version subdir + Microsoft Windows SDK version subdir. + + Return + ------ + str + subdir """ ucrtver = self.si.WindowsSdkLastVersion return ('%s\\' % ucrtver) if ucrtver else '' @@ -1060,22 +1346,32 @@ class EnvironmentInfo: @property def SdkSetup(self): """ - Microsoft Windows SDK Setup + Microsoft Windows SDK Setup. + + Return + ------ + list of str + paths """ - if self.vc_ver > 9.0: + if self.vs_ver > 9.0: return [] - return [os.path.join(self.si.WindowsSdkDir, 'Setup')] + return [join(self.si.WindowsSdkDir, 'Setup')] @property def FxTools(self): """ - Microsoft .NET Framework Tools + Microsoft .NET Framework Tools. + + Return + ------ + list of str + paths """ pi = self.pi si = self.si - if self.vc_ver <= 10.0: + if self.vs_ver <= 10.0: include32 = True include64 = not pi.target_is_x86() and not pi.current_is_x86() else: @@ -1084,102 +1380,142 @@ class EnvironmentInfo: tools = [] if include32: - tools += [os.path.join(si.FrameworkDir32, ver) + tools += [join(si.FrameworkDir32, ver) for ver in si.FrameworkVersion32] if include64: - tools += [os.path.join(si.FrameworkDir64, ver) + tools += [join(si.FrameworkDir64, ver) for ver in si.FrameworkVersion64] return tools @property def NetFxSDKLibraries(self): """ - Microsoft .Net Framework SDK Libraries + Microsoft .Net Framework SDK Libraries. + + Return + ------ + list of str + paths """ - if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: return [] arch_subdir = self.pi.target_dir(x64=True) - return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] + return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] @property def NetFxSDKIncludes(self): """ - Microsoft .Net Framework SDK Includes + Microsoft .Net Framework SDK Includes. + + Return + ------ + list of str + paths """ - if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: + if self.vs_ver < 14.0 or not self.si.NetFxSdkDir: return [] - return [os.path.join(self.si.NetFxSdkDir, r'include\um')] + return [join(self.si.NetFxSdkDir, r'include\um')] @property def VsTDb(self): """ - Microsoft Visual Studio Team System Database + Microsoft Visual Studio Team System Database. + + Return + ------ + list of str + paths """ - return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')] + return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')] @property def MSBuild(self): """ - Microsoft Build Engine + Microsoft Build Engine. + + Return + ------ + list of str + paths """ - if self.vc_ver < 12.0: + if self.vs_ver < 12.0: return [] - elif self.vc_ver < 15.0: + elif self.vs_ver < 15.0: base_path = self.si.ProgramFilesx86 arch_subdir = self.pi.current_dir(hidex86=True) else: base_path = self.si.VSInstallDir arch_subdir = '' - path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir) - build = [os.path.join(base_path, path)] + path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir) + build = [join(base_path, path)] - if self.vc_ver >= 15.0: + if self.vs_ver >= 15.0: # Add Roslyn C# & Visual Basic Compiler - build += [os.path.join(base_path, path, 'Roslyn')] + build += [join(base_path, path, 'Roslyn')] return build @property def HTMLHelpWorkshop(self): """ - Microsoft HTML Help Workshop + Microsoft HTML Help Workshop. + + Return + ------ + list of str + paths """ - if self.vc_ver < 11.0: + if self.vs_ver < 11.0: return [] - return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')] + return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')] @property def UCRTLibraries(self): """ - Microsoft Universal C Runtime SDK Libraries + Microsoft Universal C Runtime SDK Libraries. + + Return + ------ + list of str + paths """ - if self.vc_ver < 14.0: + if self.vs_ver < 14.0: return [] arch_subdir = self.pi.target_dir(x64=True) - lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') + lib = join(self.si.UniversalCRTSdkDir, 'lib') ucrtver = self._ucrt_subdir - return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] + return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] @property def UCRTIncludes(self): """ - Microsoft Universal C Runtime SDK Include + Microsoft Universal C Runtime SDK Include. + + Return + ------ + list of str + paths """ - if self.vc_ver < 14.0: + if self.vs_ver < 14.0: return [] - include = os.path.join(self.si.UniversalCRTSdkDir, 'include') - return [os.path.join(include, '%sucrt' % self._ucrt_subdir)] + include = join(self.si.UniversalCRTSdkDir, 'include') + return [join(include, '%sucrt' % self._ucrt_subdir)] @property def _ucrt_subdir(self): """ - Microsoft Universal C Runtime SDK version subdir + Microsoft Universal C Runtime SDK version subdir. + + Return + ------ + str + subdir """ ucrtver = self.si.UniversalCRTSdkLastVersion return ('%s\\' % ucrtver) if ucrtver else '' @@ -1187,31 +1523,52 @@ class EnvironmentInfo: @property def FSharp(self): """ - Microsoft Visual F# + Microsoft Visual F#. + + Return + ------ + list of str + paths """ - if self.vc_ver < 11.0 and self.vc_ver > 12.0: + if 11.0 > self.vs_ver > 12.0: return [] - return self.si.FSharpInstallDir + return [self.si.FSharpInstallDir] @property def VCRuntimeRedist(self): """ - Microsoft Visual C++ runtime redistribuable dll - """ - arch_subdir = self.pi.target_dir(x64=True) - if self.vc_ver < 15: - redist_path = self.si.VCInstallDir - vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' - else: - redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist') - vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' - - # Visual Studio 2017 is still Visual C++ 14.0 - dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver + Microsoft Visual C++ runtime redistributable dll. - vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver) - return os.path.join(redist_path, vcruntime) + Return + ------ + str + path + """ + vcruntime = 'vcruntime%d0.dll' % self.vc_ver + arch_subdir = self.pi.target_dir(x64=True).strip('\\') + + # Installation prefixes candidates + prefixes = [] + tools_path = self.si.VCInstallDir + redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist')) + if isdir(redist_path): + # Redist version may not be exactly the same as tools + redist_path = join(redist_path, listdir(redist_path)[-1]) + prefixes += [redist_path, join(redist_path, 'onecore')] + + prefixes += [join(tools_path, 'redist')] # VS14 legacy path + + # CRT directory + crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10), + # Sometime store in directory with VS version instead of VC + 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10)) + + # vcruntime path + for prefix, crt_dir in itertools.product(prefixes, crt_dirs): + path = join(prefix, arch_subdir, crt_dir, vcruntime) + if isfile(path): + return path def return_env(self, exists=True): """ @@ -1221,6 +1578,11 @@ class EnvironmentInfo: ---------- exists: bool It True, only return existing paths. + + Return + ------ + dict + environment """ env = dict( include=self._build_paths('include', @@ -1254,7 +1616,7 @@ class EnvironmentInfo: self.FSharp], exists), ) - if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist): + if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist): env['py_vcruntime_redist'] = self.VCRuntimeRedist return env @@ -1265,20 +1627,35 @@ class EnvironmentInfo: unique, extant, directories from those paths and from the environment variable. Raise an error if no paths are resolved. + + Parameters + ---------- + name: str + Environment variable name + spec_path_lists: list of str + Paths + exists: bool + It True, only return existing paths. + + Return + ------ + str + Pathsep-separated paths """ # flatten spec_path_lists spec_paths = itertools.chain.from_iterable(spec_path_lists) - env_paths = safe_env.get(name, '').split(os.pathsep) + env_paths = environ.get(name, '').split(pathsep) paths = itertools.chain(spec_paths, env_paths) - extant_paths = list(filter(os.path.isdir, paths)) if exists else paths + extant_paths = list(filter(isdir, paths)) if exists else paths if not extant_paths: msg = "%s environment variable is empty" % name.upper() raise distutils.errors.DistutilsPlatformError(msg) unique_paths = self._unique_everseen(extant_paths) - return os.pathsep.join(unique_paths) + return pathsep.join(unique_paths) # from Python docs - def _unique_everseen(self, iterable, key=None): + @staticmethod + def _unique_everseen(iterable, key=None): """ List unique elements, preserving order. Remember all elements ever seen. -- cgit v1.2.3 From 5405d1e2770e798ed755e8cc821f6b95cb480d52 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 12 Aug 2019 06:18:19 +0200 Subject: travis: update PyPy jobs to use more recent versions --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ffcad998..64d0544c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ jobs: python: 2.7 - <<: *latest_py2 env: LANG=C - - python: pypy2.7-6.0.0 + - python: pypy env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - - python: pypy3.5-6.0.0 + - python: pypy3 env: DISABLE_COVERAGE=1 - python: 3.4 - python: 3.5 -- cgit v1.2.3 From b03652f642a8ea04644eb7d5b38223148dea5611 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 13 Aug 2019 01:10:05 +0200 Subject: pkg_resources: fix ``Requirement`` hash/equality implementation Take PEP 508 direct URL into account. --- changelog.d/1814.change.rst | 1 + pkg_resources/__init__.py | 1 + pkg_resources/tests/test_resources.py | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 changelog.d/1814.change.rst diff --git a/changelog.d/1814.change.rst b/changelog.d/1814.change.rst new file mode 100644 index 00000000..c936699d --- /dev/null +++ b/changelog.d/1814.change.rst @@ -0,0 +1 @@ +Fix ``pkg_resources.Requirement`` hash/equality implementation: take PEP 508 direct URL into account. diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 1f170cfd..e75769d7 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -3109,6 +3109,7 @@ class Requirement(packaging.requirements.Requirement): self.extras = tuple(map(safe_extra, self.extras)) self.hashCmp = ( self.key, + self.url, self.specifier, frozenset(self.extras), str(self.marker) if self.marker else None, diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py index 86afcf74..42c801a7 100644 --- a/pkg_resources/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -520,6 +520,11 @@ class TestRequirements: assert r1 == r2 assert str(r1) == str(r2) assert str(r2) == "Twisted==1.2c1,>=1.2" + assert ( + Requirement("Twisted") + != + Requirement("Twisted @ https://localhost/twisted.zip") + ) def testBasicContains(self): r = Requirement("Twisted>=1.2") @@ -546,11 +551,23 @@ class TestRequirements: == hash(( "twisted", + None, packaging.specifiers.SpecifierSet(">=1.2"), frozenset(["foo", "bar"]), None )) ) + assert ( + hash(Requirement.parse("Twisted @ https://localhost/twisted.zip")) + == + hash(( + "twisted", + "https://localhost/twisted.zip", + packaging.specifiers.SpecifierSet(), + frozenset(), + None + )) + ) def testVersionEquality(self): r1 = Requirement.parse("foo==0.3a2") -- cgit v1.2.3 From cd92d8a98f388b33fd7d5f24d096408408426b0c Mon Sep 17 00:00:00 2001 From: Christopher Head Date: Mon, 12 Aug 2019 21:37:34 -0700 Subject: Document using asterisk in package_data section --- docs/setuptools.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 2e7fe3bd..3509a301 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2416,7 +2416,7 @@ tests_require list-semi include_package_data bool packages find:, find_namespace:, list-comma package_dir dict -package_data section +package_data section (1) exclude_package_data section namespace_packages list-comma py_modules list-comma @@ -2433,6 +2433,10 @@ data_files dict 40.6.0 **find_namespace directive** - The ``find_namespace:`` directive is supported since Python >=3.3. +Notes: +1. In the `package_data` section, a key named with a single asterisk (`*`) +refers to all packages, in lieu of the empty string used in `setup.py`. + Configuration API ================= -- cgit v1.2.3 From 95d72cc558c7dddef164eb09a880b95e2f484bee Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Aug 2019 09:42:47 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.0.1=20=E2=86=92=2041.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 13 +++++++++++++ changelog.d/1697.change.rst | 1 - changelog.d/1749.change.rst | 1 - changelog.d/1750.change.rst | 1 - changelog.d/1756.change.rst | 1 - changelog.d/1769.change.rst | 1 - changelog.d/1776.doc.rst | 1 - changelog.d/1788.change.rst | 1 - changelog.d/1790.change.rst | 2 -- setup.cfg | 2 +- 11 files changed, 15 insertions(+), 11 deletions(-) delete mode 100644 changelog.d/1697.change.rst delete mode 100644 changelog.d/1749.change.rst delete mode 100644 changelog.d/1750.change.rst delete mode 100644 changelog.d/1756.change.rst delete mode 100644 changelog.d/1769.change.rst delete mode 100644 changelog.d/1776.doc.rst delete mode 100644 changelog.d/1788.change.rst delete mode 100644 changelog.d/1790.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 87acb5ef..5a453660 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.0.1 +current_version = 41.1.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 9da22537..9f9603c0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +v41.1.0 +------- + +* #1697: Moved most of the constants from setup.py to setup.cfg +* #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory. +* #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist. +* #1756: Forse metadata-version >= 1.2. when project urls are present. +* #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings. +* #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9. +* #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file. +* #1776: Use license classifiers rather than the license field. + + v41.0.1 ------- diff --git a/changelog.d/1697.change.rst b/changelog.d/1697.change.rst deleted file mode 100644 index 44818b3c..00000000 --- a/changelog.d/1697.change.rst +++ /dev/null @@ -1 +0,0 @@ -Moved most of the constants from setup.py to setup.cfg diff --git a/changelog.d/1749.change.rst b/changelog.d/1749.change.rst deleted file mode 100644 index de678072..00000000 --- a/changelog.d/1749.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory. diff --git a/changelog.d/1750.change.rst b/changelog.d/1750.change.rst deleted file mode 100644 index 7a22229e..00000000 --- a/changelog.d/1750.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist. diff --git a/changelog.d/1756.change.rst b/changelog.d/1756.change.rst deleted file mode 100644 index 5c908d35..00000000 --- a/changelog.d/1756.change.rst +++ /dev/null @@ -1 +0,0 @@ -Forse metadata-version >= 1.2. when project urls are present. diff --git a/changelog.d/1769.change.rst b/changelog.d/1769.change.rst deleted file mode 100644 index d48a23b6..00000000 --- a/changelog.d/1769.change.rst +++ /dev/null @@ -1 +0,0 @@ -Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings. diff --git a/changelog.d/1776.doc.rst b/changelog.d/1776.doc.rst deleted file mode 100644 index d4f1dbca..00000000 --- a/changelog.d/1776.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Use license classifiers rather than the license field. diff --git a/changelog.d/1788.change.rst b/changelog.d/1788.change.rst deleted file mode 100644 index d8a49fd4..00000000 --- a/changelog.d/1788.change.rst +++ /dev/null @@ -1 +0,0 @@ -Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9. diff --git a/changelog.d/1790.change.rst b/changelog.d/1790.change.rst deleted file mode 100644 index e4a7998d..00000000 --- a/changelog.d/1790.change.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added the file path to the error message when a ``UnicodeDecodeError`` occurs -while reading a metadata file. diff --git a/setup.cfg b/setup.cfg index ff87464b..3bcf4429 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.0.1 +version = 41.1.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 42bd00ada0e0f47c2446d6d1b750eb97eba45640 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Aug 2019 09:47:19 -0400 Subject: Prefer token for automated releases --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 64d0544c..13815fd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,9 +35,9 @@ jobs: on: tags: true all_branches: true - user: jaraco + user: __token__ password: - secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g= + secure: lBgpqft5tKMPRGefCPScPLWNKKGPF89kYrGt3RogqZrb9xB9+52bNrHgt0M5H2EQIRW9IC7/RFRXpT/dF4N8CT1hsljSVQxTQxiGazEYPSdEfOJ2CJxqs0smiL5Ck1T8EQeiuqRSM+/RFEDNaTjp+fe4HhSoPhea6TojAXRSzKdmcDLCwSJOujDccz0yMOagibnqfhO6ZsIv1LUUHWBeeUVrmx+3lz6uAqmNIOQ6hn6YTP4HLoEXTN+IIeQGIzXEZwY2Z/OXakiugxeNZOgRlkikT32KXyZ01hOhOdPOWrtv+q9tAAdqUeQmtdfFb3zy2vBr6bJkvplI5Obh/qHTphWA3iOTOoCICFYPhXY/npztwwqG7jFBRsZbWo1zuP92zUji6OoxK0ezkqEQcIhu+qsUERmsuowriNyFJZK8zVRdGc7JGaZvYe1h9k4l44J+VEIXUurKWji8BpL7dxq/ZPZRf2r5P2JqhU1VkGXrNgfze/N7U1Z1oUkHo1g/5InOl8nDN4Ul0oRXm9tezk5vYO/RMs9hphTdzda4iETikVjnXyHScSLWwI0UqF5MQdKBYsOCJM9deirWG2gXXspkW4HPkYUUe1pTCkaqoQ9+fWHcKTeDVT/52iuCsaSOJccOItyQqH/4nS3i3LaEf11DPMKKL58pbkoMD56p9iSrCCs= distributions: release skip_cleanup: true skip_upload_docs: true -- cgit v1.2.3 From e40a6035d9e688d5a34077132b49c48dc74932f4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Aug 2019 13:39:28 -0400 Subject: Revert "Prefer token for automated releases" This reverts commit 42bd00ada0e0f47c2446d6d1b750eb97eba45640. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13815fd4..64d0544c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,9 +35,9 @@ jobs: on: tags: true all_branches: true - user: __token__ + user: jaraco password: - secure: lBgpqft5tKMPRGefCPScPLWNKKGPF89kYrGt3RogqZrb9xB9+52bNrHgt0M5H2EQIRW9IC7/RFRXpT/dF4N8CT1hsljSVQxTQxiGazEYPSdEfOJ2CJxqs0smiL5Ck1T8EQeiuqRSM+/RFEDNaTjp+fe4HhSoPhea6TojAXRSzKdmcDLCwSJOujDccz0yMOagibnqfhO6ZsIv1LUUHWBeeUVrmx+3lz6uAqmNIOQ6hn6YTP4HLoEXTN+IIeQGIzXEZwY2Z/OXakiugxeNZOgRlkikT32KXyZ01hOhOdPOWrtv+q9tAAdqUeQmtdfFb3zy2vBr6bJkvplI5Obh/qHTphWA3iOTOoCICFYPhXY/npztwwqG7jFBRsZbWo1zuP92zUji6OoxK0ezkqEQcIhu+qsUERmsuowriNyFJZK8zVRdGc7JGaZvYe1h9k4l44J+VEIXUurKWji8BpL7dxq/ZPZRf2r5P2JqhU1VkGXrNgfze/N7U1Z1oUkHo1g/5InOl8nDN4Ul0oRXm9tezk5vYO/RMs9hphTdzda4iETikVjnXyHScSLWwI0UqF5MQdKBYsOCJM9deirWG2gXXspkW4HPkYUUe1pTCkaqoQ9+fWHcKTeDVT/52iuCsaSOJccOItyQqH/4nS3i3LaEf11DPMKKL58pbkoMD56p9iSrCCs= + secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g= distributions: release skip_cleanup: true skip_upload_docs: true -- cgit v1.2.3 From 05f42a2c14275937250c99478c94b20171d14aeb Mon Sep 17 00:00:00 2001 From: A_Rog Date: Wed, 14 Aug 2019 00:40:12 +0100 Subject: Fixed html sidebars to supported version in Sphinx (#1804) --- .travis.yml | 2 ++ changelog.d/1565.misc.rst | 2 ++ docs/conf.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1565.misc.rst diff --git a/.travis.yml b/.travis.yml index 64d0544c..8441261d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ jobs: - <<: *latest_py3 env: LANG=C - python: 3.8-dev + - <<: *latest_py3 + env: TOXENV=docs DISABLE_COVERAGE=1 - <<: *default_py stage: deploy (to PyPI for tagged commits) if: tag IS present diff --git a/changelog.d/1565.misc.rst b/changelog.d/1565.misc.rst new file mode 100644 index 00000000..ca15573a --- /dev/null +++ b/changelog.d/1565.misc.rst @@ -0,0 +1,2 @@ +Changed html_sidebars from string to list of string as per +https://www.sphinx-doc.org/en/master/changes.html#id58 diff --git a/docs/conf.py b/docs/conf.py index c7eb6d3f..cbd19fb4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,7 +69,7 @@ html_theme_path = ['_theme'] html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -html_sidebars = {'index': 'indexsidebar.html'} +html_sidebars = {'index': ['relations.html', 'sourcelink.html', 'indexsidebar.html', 'searchbox.html']} # If false, no module index is generated. html_use_modindex = False -- cgit v1.2.3 From eb7436b36f9967d1becd2b822da548bd59b35d05 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 8 Jul 2019 09:32:21 -0700 Subject: Fix some usage of deprecated `imp` module --- changelog.d/479.change.rst | 1 + setuptools/command/build_ext.py | 10 ++++++++-- setuptools/command/install_lib.py | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 changelog.d/479.change.rst diff --git a/changelog.d/479.change.rst b/changelog.d/479.change.rst new file mode 100644 index 00000000..d494c0fc --- /dev/null +++ b/changelog.d/479.change.rst @@ -0,0 +1 @@ +Remove some usage of the deprecated ``imp`` module. diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index 60a8a32f..daa8e4fe 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -1,7 +1,6 @@ import os import sys import itertools -import imp from distutils.command.build_ext import build_ext as _du_build_ext from distutils.file_util import copy_file from distutils.ccompiler import new_compiler @@ -12,6 +11,13 @@ from distutils import log from setuptools.extension import Library from setuptools.extern import six +if six.PY2: + import imp + + EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] +else: + from importlib.machinery import EXTENSION_SUFFIXES + try: # Attempt to use Cython for building extensions, if available from Cython.Distutils.build_ext import build_ext as _build_ext @@ -64,7 +70,7 @@ if_dl = lambda s: s if have_rtld else '' def get_abi3_suffix(): """Return the file extension for an abi3-compliant Extension()""" - for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION): + for suffix in EXTENSION_SUFFIXES: if '.abi3' in suffix: # Unix return suffix elif suffix == '.pyd': # Windows diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index 2b31c3e3..07d65933 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -1,5 +1,5 @@ import os -import imp +import sys from itertools import product, starmap import distutils.command.install_lib as orig @@ -74,10 +74,10 @@ class install_lib(orig.install_lib): yield '__init__.pyc' yield '__init__.pyo' - if not hasattr(imp, 'get_tag'): + if not hasattr(sys, 'implementation'): return - base = os.path.join('__pycache__', '__init__.' + imp.get_tag()) + base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag) yield base + '.pyc' yield base + '.pyo' yield base + '.opt-1.pyc' -- cgit v1.2.3 From 43add1d3f5138e38adc4940647cc6eae94fb6123 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 17 Aug 2019 19:14:48 -0700 Subject: Fixes for python3.10 --- changelog.d/1824.change.rst | 1 + pkg_resources/__init__.py | 2 +- pkg_resources/api_tests.txt | 2 +- pkg_resources/tests/test_resources.py | 2 +- setup.py | 2 +- setuptools/command/bdist_egg.py | 2 +- setuptools/command/easy_install.py | 6 +++--- setuptools/package_index.py | 2 +- setuptools/tests/test_bdist_egg.py | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 changelog.d/1824.change.rst diff --git a/changelog.d/1824.change.rst b/changelog.d/1824.change.rst new file mode 100644 index 00000000..5f609036 --- /dev/null +++ b/changelog.d/1824.change.rst @@ -0,0 +1 @@ +Fix tests when running under ``python3.10``. diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 1f170cfd..fb68813e 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -333,7 +333,7 @@ class UnknownExtra(ResolutionError): _provider_factories = {} -PY_MAJOR = sys.version[:3] +PY_MAJOR = '{}.{}'.format(*sys.version_info) EGG_DIST = 3 BINARY_DIST = 2 SOURCE_DIST = 1 diff --git a/pkg_resources/api_tests.txt b/pkg_resources/api_tests.txt index 0a75170e..7ae5a038 100644 --- a/pkg_resources/api_tests.txt +++ b/pkg_resources/api_tests.txt @@ -36,7 +36,7 @@ Distributions have various introspectable attributes:: >>> dist.version '0.9' - >>> dist.py_version == sys.version[:3] + >>> dist.py_version == '{}.{}'.format(*sys.version_info) True >>> print(dist.platform) diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py index 86afcf74..7063ed3d 100644 --- a/pkg_resources/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -116,7 +116,7 @@ class TestDistro: self.checkFooPkg(d) d = Distribution("/some/path") - assert d.py_version == sys.version[:3] + assert d.py_version == '{}.{}'.format(*sys.version_info) assert d.platform is None def testDistroParse(self): diff --git a/setup.py b/setup.py index f5030dd6..d97895fc 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def _gen_console_scripts(): if any(os.environ.get(var) not in (None, "", "0") for var in var_names): return tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main" - yield tmpl.format(shortver=sys.version[:3]) + yield tmpl.format(shortver='{}.{}'.format(*sys.version_info)) package_data = dict( diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py index 9f8df917..98470f17 100644 --- a/setuptools/command/bdist_egg.py +++ b/setuptools/command/bdist_egg.py @@ -284,7 +284,7 @@ class bdist_egg(Command): "or refer to a module" % (ep,) ) - pyver = sys.version[:3] + pyver = '{}.{}'.format(*sys.version_info) pkg = ep.module_name full = '.'.join(ep.attrs) base = ep.attrs[0] diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 06c98271..593ed777 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -241,7 +241,7 @@ class easy_install(Command): """ Render the Setuptools version and installation details, then exit. """ - ver = sys.version[:3] + ver = '{}.{}'.format(*sys.version_info) dist = get_distribution('setuptools') tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})' print(tmpl.format(**locals())) @@ -1412,7 +1412,7 @@ def get_site_dirs(): os.path.join( prefix, "lib", - "python" + sys.version[:3], + "python{}.{}".format(*sys.version_info), "site-packages", ), os.path.join(prefix, "lib", "site-python"), @@ -1433,7 +1433,7 @@ def get_site_dirs(): home, 'Library', 'Python', - sys.version[:3], + '{}.{}'.format(*sys.version_info), 'site-packages', ) sitedirs.append(home_sp) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 6b06f2ca..f419d471 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -46,7 +46,7 @@ __all__ = [ _SOCKET_TIMEOUT = 15 _tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" -user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools) +user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools) def parse_requirement_arg(spec): diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py index 54742aa6..fb5b90b1 100644 --- a/setuptools/tests/test_bdist_egg.py +++ b/setuptools/tests/test_bdist_egg.py @@ -42,7 +42,7 @@ class Test: # let's see if we got our egg link at the right place [content] = os.listdir('dist') - assert re.match(r'foo-0.0.0-py[23].\d.egg$', content) + assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content) @pytest.mark.xfail( os.environ.get('PYTHONDONTWRITEBYTECODE'), -- cgit v1.2.3 From 35233197be40eafa1e81d71aedeb82af1a761f54 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Aug 2019 04:49:30 -0400 Subject: Once again, use token for cutting releases. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8441261d..8b7cece8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,9 @@ jobs: on: tags: true all_branches: true - user: jaraco + user: __token__ password: - secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g= + secure: FSp9KU+pdvWPxBOaxe6BNmcJ9y8259G3/NdTJ00r0qx/xMLpSneGjpuLqoD6BL2JoM6gRwurwakWoH/9Ah+Di7afETjMnL6WJKtDZ+Uu3YLx3ss7/FlhVz6zmVTaDJUzuo9dGr//qLBQTIxVjGYfQelRJyfMAXtrYWdeT/4489E45lMw+86Z/vnSBOxs4lWekeQW5Gem0cDViWu67RRiGkAEvrYVwuImMr2Dyhpv+l/mQGQIS/ezXuAEFToE6+q8VUVe/aK498Qovdc+O4M7OYk1JouFpffZ3tVZ6iWHQFcR11480UdI6VCIcFpPvGC/J8MWUWLjq7YOm0X9jPXgdYMUQLAP4clFgUr2qNoRSKWfuQlNdVVuS2htYcjJ3eEl90FhcIZKp+WVMrypRPOQJ8CBielZEs0dhytRrZSaJC1BNq25O/BPzws8dL8hYtoXsM6I3Zv5cZgdyqyq/eOEMCX7Cetv6do0U41VGEV5UohvyyuwH5l9GCuPREpY3sXayPg8fw7XcPjvvzSVyjcUT/ePW8sfnAyWZnngjweAn6dK8IFGPuSPQdlos78uxeUOvCVUW0xv/0m4lX73yoHdVVdLbu1MJTyibFGec86Bew9JqIcDlhHaIJ9ihZ9Z9tOtvp1cuNyKYE4kvmOtumDDicEw4DseYn2z5sZDTYTBsKY= distributions: release skip_cleanup: true skip_upload_docs: true -- cgit v1.2.3 From a3e6e68365079642d8949b26ecb810751cc15ef1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Aug 2019 04:50:43 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.1.0=20=E2=86=92=2041.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 8 ++++++++ changelog.d/1565.misc.rst | 2 -- changelog.d/479.change.rst | 1 - setup.cfg | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 changelog.d/1565.misc.rst delete mode 100644 changelog.d/479.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5a453660..5e7e8ee4 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.1.0 +current_version = 41.2.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 9f9603c0..84007b31 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,11 @@ +v41.2.0 +------- + +* #479: Remove some usage of the deprecated ``imp`` module. +* #1565: Changed html_sidebars from string to list of string as per + https://www.sphinx-doc.org/en/master/changes.html#id58 + + v41.1.0 ------- diff --git a/changelog.d/1565.misc.rst b/changelog.d/1565.misc.rst deleted file mode 100644 index ca15573a..00000000 --- a/changelog.d/1565.misc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Changed html_sidebars from string to list of string as per -https://www.sphinx-doc.org/en/master/changes.html#id58 diff --git a/changelog.d/479.change.rst b/changelog.d/479.change.rst deleted file mode 100644 index d494c0fc..00000000 --- a/changelog.d/479.change.rst +++ /dev/null @@ -1 +0,0 @@ -Remove some usage of the deprecated ``imp`` module. diff --git a/setup.cfg b/setup.cfg index 3bcf4429..96792dd3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.1.0 +version = 41.2.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 7fa2fcf95430ba41959e16c19bd77795b2b4fc4a Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 2 Sep 2019 00:30:36 +0200 Subject: Add GitHub Actions CI/CD test suite workflow --- .github/workflows/python-tests.yml | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/python-tests.yml diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 00000000..00250ff1 --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,75 @@ +name: Test suite + +on: + push: + pull_request: + schedule: + - cron: 1 0 * * * # Run daily at 0:01 UTC + +jobs: + tests: + name: 👷 + runs-on: ${{ matrix.os }} + strategy: + # max-parallel: 5 + matrix: + python-version: + - 3.7 + - 3.6 + - 3.5 + - 2.7 + os: + - ubuntu-18.04 + - ubuntu-16.04 + - macOS-10.14 + - windows-2019 + - windows-2016 + env: + - TOXENV: python + + steps: + - uses: actions/checkout@master + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + version: ${{ matrix.python-version }} + - name: Upgrade pip/setuptools/wheel + run: >- + python + -m pip install + --disable-pip-version-check + --upgrade + pip setuptools wheel + - name: Install tox + run: >- + python -m pip install --upgrade tox tox-venv + - name: Log installed dists + run: >- + python -m pip freeze --all + - name: Log env vars + run: >- + 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 + if: >- + ! startsWith(matrix.os, 'windows-') + run: >- + ! grep pyc setuptools.egg-info/SOURCES.txt + + - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' + run: | + python -m tox --parallel auto --notest + env: ${{ matrix.env }} + - name: Test with tox + run: | + ${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 + python -m tox \ + --parallel 0 \ + -- \ + --cov + env: ${{ matrix.env }} -- cgit v1.2.3 From 67dd74ad0fd84714c6710168c7e747630276230a Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 2 Sep 2019 00:59:00 +0200 Subject: Temporary comment out win jobs --- .github/workflows/python-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 00250ff1..c5ab4dd5 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -22,8 +22,8 @@ jobs: - ubuntu-18.04 - ubuntu-16.04 - macOS-10.14 - - windows-2019 - - windows-2016 + # - windows-2019 + # - windows-2016 env: - TOXENV: python -- cgit v1.2.3 From 440238ebb54ba4b05d8656e0bdf333d53627c198 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 2 Sep 2019 16:14:01 +0200 Subject: Don't skip missing interpreters in tox --- .github/workflows/python-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index c5ab4dd5..eb750a45 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -63,7 +63,7 @@ jobs: - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' run: | - python -m tox --parallel auto --notest + python -m tox --parallel auto --notest --skip-missing-interpreters false env: ${{ matrix.env }} - name: Test with tox run: | -- cgit v1.2.3 From 04940b6e09bb5054666442196383c2fe9e5a09a2 Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Tue, 3 Sep 2019 21:36:49 +0300 Subject: Fix typo in CHANGES.rst --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 84007b31..dd47cb43 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,7 @@ v41.1.0 * #1697: Moved most of the constants from setup.py to setup.cfg * #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory. * #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist. -* #1756: Forse metadata-version >= 1.2. when project urls are present. +* #1756: Force metadata-version >= 1.2. when project urls are present. * #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings. * #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9. * #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file. -- cgit v1.2.3 From ca0ee009f81a460b483c7e451e58dfdcd7787045 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 16:39:57 +0100 Subject: Add test capturing failure. Ref #1787. --- setuptools/tests/test_config.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index bc97664d..42187138 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -819,6 +819,18 @@ class TestOptions: ] assert sorted(dist.data_files) == sorted(expected) + def test_python_requires_invalid(self, tmpdir): + fake_env( + tmpdir, + DALS(""" + [options] + python_requires=invalid + """), + ) + with pytest.raises(DistutilsOptionError): + with get_dist(tmpdir) as dist: + dist.parse_config_files() + saved_dist_init = _Distribution.__init__ -- cgit v1.2.3 From b31777cd50c7cb59b4ef6c22bd014f0229ef32fa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 16:52:58 +0100 Subject: Add more tests for valid behavior. Expand exception, any should do. --- setuptools/tests/test_config.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 42187138..1b94a586 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -819,6 +819,28 @@ class TestOptions: ] assert sorted(dist.data_files) == sorted(expected) + def test_python_requires_simple(self, tmpdir): + fake_env( + tmpdir, + DALS(""" + [options] + python_requires=>=2.7 + """), + ) + with get_dist(tmpdir) as dist: + dist.parse_config_files() + + def test_python_requires_compound(self, tmpdir): + fake_env( + tmpdir, + DALS(""" + [options] + python_requires=>=2.7,!=3.0.* + """), + ) + with get_dist(tmpdir) as dist: + dist.parse_config_files() + def test_python_requires_invalid(self, tmpdir): fake_env( tmpdir, @@ -827,7 +849,7 @@ class TestOptions: python_requires=invalid """), ) - with pytest.raises(DistutilsOptionError): + with pytest.raises(Exception): with get_dist(tmpdir) as dist: dist.parse_config_files() -- cgit v1.2.3 From c3df086ed3570e7065e6935a52d95c8cdef2b071 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 16:54:07 +0100 Subject: Ensure that python_requires is checked during option processing. Fixes #1787. --- setuptools/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setuptools/config.py b/setuptools/config.py index b6626043..2d50e25e 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -12,6 +12,7 @@ from importlib import import_module from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.extern.packaging.version import LegacyVersion, parse +from setuptools.extern.packaging.specifiers import SpecifierSet from setuptools.extern.six import string_types, PY3 @@ -554,6 +555,7 @@ class ConfigOptionsHandler(ConfigHandler): 'packages': self._parse_packages, 'entry_points': self._parse_file, 'py_modules': parse_list, + 'python_requires': SpecifierSet, } def _parse_packages(self, value): -- cgit v1.2.3 From 895d18c17d15f443c6d8953a176aa6c4df354c7d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 16:55:09 +0100 Subject: Update changelog. Ref #1787. --- changelog.d/1847.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1847.change.rst diff --git a/changelog.d/1847.change.rst b/changelog.d/1847.change.rst new file mode 100644 index 00000000..2a6b9c77 --- /dev/null +++ b/changelog.d/1847.change.rst @@ -0,0 +1 @@ +Now traps errors when invalid ``python_requires`` values are supplied. -- cgit v1.2.3 From 45b83cd189db31a0315a4297053feab77d8618b4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 18:14:53 +0100 Subject: Update changelog. Ref #1690. --- changelog.d/1690.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1690.change.rst diff --git a/changelog.d/1690.change.rst b/changelog.d/1690.change.rst new file mode 100644 index 00000000..ecc51c55 --- /dev/null +++ b/changelog.d/1690.change.rst @@ -0,0 +1 @@ +When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+. -- cgit v1.2.3 From 6f962a07f586162d05e087a90ea8f44461772070 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 6 Oct 2019 20:42:14 -0400 Subject: Refresh vendored packages (ordereddict 3.1.1) --- setuptools/_vendor/ordered_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setuptools/_vendor/ordered_set.py b/setuptools/_vendor/ordered_set.py index d257470b..14876000 100644 --- a/setuptools/_vendor/ordered_set.py +++ b/setuptools/_vendor/ordered_set.py @@ -87,14 +87,14 @@ class OrderedSet(MutableSet, Sequence): """ if isinstance(index, slice) and index == SLICE_ALL: return self.copy() + elif is_iterable(index): + return [self.items[i] for i in index] elif hasattr(index, "__index__") or isinstance(index, slice): result = self.items[index] if isinstance(result, list): return self.__class__(result) else: return result - elif is_iterable(index): - return [self.items[i] for i in index] else: raise TypeError("Don't know how to index an OrderedSet by %r" % index) -- cgit v1.2.3 From 53d662a9de0b8d449d167bf1c5cf291a2fecb094 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 6 Oct 2019 21:17:53 -0400 Subject: Allow 'long_description_content_type' warnings for new versions of packaging. Fixes #1858. --- changelog.d/1858.misc.rst | 1 + setuptools/tests/test_integration.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/1858.misc.rst diff --git a/changelog.d/1858.misc.rst b/changelog.d/1858.misc.rst new file mode 100644 index 00000000..b16ac1dd --- /dev/null +++ b/changelog.d/1858.misc.rst @@ -0,0 +1 @@ +Fixed failing integration test triggered by 'long_description_content_type' in packaging. diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 1e132188..1c0b2b18 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -143,6 +143,7 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep): 'tests_require', 'python_requires', 'install_requires', + 'long_description_content_type', ] assert not match or match.group(1).strip('"\'') in allowed_unknowns -- cgit v1.2.3 From da3ccf5f648a5836ce5e9b0747a263860ae1dfaa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 6 Oct 2019 21:22:05 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.2.0=20=E2=86=92=2041.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 7 +++++++ changelog.d/1690.change.rst | 1 - changelog.d/1858.misc.rst | 1 - setup.cfg | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/1690.change.rst delete mode 100644 changelog.d/1858.misc.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5e7e8ee4..37b22446 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.2.0 +current_version = 41.3.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index dd47cb43..883f4f2b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v41.3.0 +------- + +* #1690: When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+. +* #1858: Fixed failing integration test triggered by 'long_description_content_type' in packaging. + + v41.2.0 ------- diff --git a/changelog.d/1690.change.rst b/changelog.d/1690.change.rst deleted file mode 100644 index ecc51c55..00000000 --- a/changelog.d/1690.change.rst +++ /dev/null @@ -1 +0,0 @@ -When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+. diff --git a/changelog.d/1858.misc.rst b/changelog.d/1858.misc.rst deleted file mode 100644 index b16ac1dd..00000000 --- a/changelog.d/1858.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed failing integration test triggered by 'long_description_content_type' in packaging. diff --git a/setup.cfg b/setup.cfg index 96792dd3..77a35de0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.2.0 +version = 41.3.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 2e3c34f669c5272d036ef7edc58871c96663d587 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 6 Oct 2019 21:29:32 -0400 Subject: Clarify the scope of the change. --- changelog.d/1847.change.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/1847.change.rst b/changelog.d/1847.change.rst index 2a6b9c77..d3f7724e 100644 --- a/changelog.d/1847.change.rst +++ b/changelog.d/1847.change.rst @@ -1 +1 @@ -Now traps errors when invalid ``python_requires`` values are supplied. +In declarative config, now traps errors when invalid ``python_requires`` values are supplied. -- cgit v1.2.3 From 5eee6b4b523a6d2ae16ef913c01302f951b4f614 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 6 Oct 2019 21:30:46 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.3.0=20=E2=86=92=2041.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 6 ++++++ changelog.d/1847.change.rst | 1 - setup.cfg | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/1847.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 37b22446..d6768cb8 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.3.0 +current_version = 41.4.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 883f4f2b..ecde25a5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v41.4.0 +------- + +* #1847: In declarative config, now traps errors when invalid ``python_requires`` values are supplied. + + v41.3.0 ------- diff --git a/changelog.d/1847.change.rst b/changelog.d/1847.change.rst deleted file mode 100644 index d3f7724e..00000000 --- a/changelog.d/1847.change.rst +++ /dev/null @@ -1 +0,0 @@ -In declarative config, now traps errors when invalid ``python_requires`` values are supplied. diff --git a/setup.cfg b/setup.cfg index 77a35de0..a55106a5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.3.0 +version = 41.4.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 026f9b75544c53636fd692af386730553740a511 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 14 Aug 2019 02:43:53 +0200 Subject: docs: mention dependency links support was dropped in pip 19.0 --- docs/setuptools.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 2e7fe3bd..8cfb5f13 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -675,6 +675,10 @@ using ``setup.py develop``.) Dependencies that aren't in PyPI -------------------------------- +.. warning:: + Dependency links support has been dropped by pip starting with version + 19.0 (released 2019-01-22). + If your project depends on packages that don't exist on PyPI, you may still be able to depend on them, as long as they are available for download as: -- cgit v1.2.3 From d2db805b008346ba9aa34d8fb36faf2d019eed64 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 14 Aug 2019 02:44:20 +0200 Subject: docs: mention eggs are deprecated and not supported by pip --- docs/setuptools.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 8cfb5f13..26a3044e 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1693,6 +1693,9 @@ file locations. ``bdist_egg`` - Create a Python Egg for the project =================================================== +.. warning:: + **eggs** are deprecated in favor of wheels, and not supported by pip. + This command generates a Python Egg (``.egg`` file) for the project. Python Eggs are the preferred binary distribution format for EasyInstall, because they are cross-platform (for "pure" packages), directly importable, and contain -- cgit v1.2.3 From 8b3a8d08da9780d16dce7d62433f35dbb4cc55ad Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 7 Oct 2019 18:58:40 +0200 Subject: add news entry --- changelog.d/1860.doc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1860.doc.rst diff --git a/changelog.d/1860.doc.rst b/changelog.d/1860.doc.rst new file mode 100644 index 00000000..f3554643 --- /dev/null +++ b/changelog.d/1860.doc.rst @@ -0,0 +1 @@ +Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0. -- cgit v1.2.3 From 734d09c5a3f48338b6a3d7687c5f9d0ed02ab479 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 7 Oct 2019 16:36:54 -0400 Subject: Pin ordered-set to current version for consistency. --- setuptools/_vendor/vendored.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index 379aae56..5731b424 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -1,4 +1,4 @@ packaging==16.8 pyparsing==2.2.1 six==1.10.0 -ordered-set +ordered-set==3.1.1 -- cgit v1.2.3 From d7810a901382b827146874704f33bce896e1fb21 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 10 Aug 2019 02:18:34 +0200 Subject: wheel: silence info trace when writing `requires.txt` --- setuptools/wheel.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/setuptools/wheel.py b/setuptools/wheel.py index e11f0a1d..2982926a 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -1,6 +1,7 @@ """Wheels support.""" from distutils.util import get_platform +from distutils import log import email import itertools import os @@ -162,11 +163,17 @@ class Wheel: extras_require=extras_require, ), ) - write_requirements( - setup_dist.get_command_obj('egg_info'), - None, - os.path.join(egg_info, 'requires.txt'), - ) + # Temporarily disable info traces. + log_threshold = log._global_log.threshold + log.set_threshold(log.WARN) + try: + write_requirements( + setup_dist.get_command_obj('egg_info'), + None, + os.path.join(egg_info, 'requires.txt'), + ) + finally: + log.set_threshold(log_threshold) @staticmethod def _move_data_entries(destination_eggdir, dist_data): -- cgit v1.2.3 From 16a3ef93fc66373f6c5f4da12303dd111403fcb1 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 17 Sep 2018 23:40:12 +0200 Subject: wheel: fix installation of empty namespace package --- changelog.d/1861.change.rst | 1 + setuptools/tests/test_wheel.py | 28 ++++++++++++++++++++++++++++ setuptools/wheel.py | 4 +++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1861.change.rst diff --git a/changelog.d/1861.change.rst b/changelog.d/1861.change.rst new file mode 100644 index 00000000..5a4e0a56 --- /dev/null +++ b/changelog.d/1861.change.rst @@ -0,0 +1 @@ +Fix empty namespace package installation from wheel. diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index e85a4a7e..d50816c2 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -450,6 +450,34 @@ WHEEL_INSTALL_TESTS = ( }), ), + dict( + id='empty_namespace_package', + file_defs={ + 'foobar': { + '__init__.py': "__import__('pkg_resources').declare_namespace(__name__)", + }, + }, + setup_kwargs=dict( + namespace_packages=['foobar'], + packages=['foobar'], + ), + install_tree=flatten_tree({ + 'foo-1.0-py{py_version}.egg': [ + 'foo-1.0-py{py_version}-nspkg.pth', + {'EGG-INFO': [ + 'PKG-INFO', + 'RECORD', + 'WHEEL', + 'namespace_packages.txt', + 'top_level.txt', + ]}, + {'foobar': [ + '__init__.py', + ]}, + ] + }), + ), + dict( id='data_in_package', file_defs={ diff --git a/setuptools/wheel.py b/setuptools/wheel.py index 2982926a..22eec05e 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -213,6 +213,8 @@ class Wheel: for mod in namespace_packages: mod_dir = os.path.join(destination_eggdir, *mod.split('.')) mod_init = os.path.join(mod_dir, '__init__.py') - if os.path.exists(mod_dir) and not os.path.exists(mod_init): + if not os.path.exists(mod_dir): + os.mkdir(mod_dir) + if not os.path.exists(mod_init): with open(mod_init, 'w') as fp: fp.write(NAMESPACE_PACKAGE_INIT) -- cgit v1.2.3 From cb8769d7d1a694d37194c44b98c543b2d5d38fc5 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 26 Jun 2019 22:25:03 +0200 Subject: minor cleanup --- setuptools/command/easy_install.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 06c98271..d7b7566c 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -1180,8 +1180,7 @@ class easy_install(Command): # to the setup.cfg file. ei_opts = self.distribution.get_option_dict('easy_install').copy() fetch_directives = ( - 'find_links', 'site_dirs', 'index_url', 'optimize', - 'site_dirs', 'allow_hosts', + 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts', ) fetch_options = {} for key, val in ei_opts.items(): -- cgit v1.2.3 From 0d831c90e345ba6e7d0f82954f0cec5bdaa8501b Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 10 Aug 2019 03:57:58 +0200 Subject: improve workaround for #1644 Make it possible to use a more recent version of pip for tests. --- pytest.ini | 2 +- tests/requirements.txt | 1 - tools/tox_pip.py | 29 +++++++++++++++++++++++++++++ tox.ini | 14 ++++++++------ 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 tools/tox_pip.py diff --git a/pytest.ini b/pytest.ini index 612fb91f..764a55dd 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX -norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .* +norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern tools .* flake8-ignore = setuptools/site-patch.py F821 setuptools/py*compat.py F811 diff --git a/tests/requirements.txt b/tests/requirements.txt index cb3e6726..1f70adee 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -9,4 +9,3 @@ 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. diff --git a/tools/tox_pip.py b/tools/tox_pip.py new file mode 100644 index 00000000..1117f996 --- /dev/null +++ b/tools/tox_pip.py @@ -0,0 +1,29 @@ +import os +import shutil +import subprocess +import sys +from glob import glob + +VIRTUAL_ENV = os.environ['VIRTUAL_ENV'] +TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip') + + +def pip(args): + # First things first, get a recent (stable) version of pip. + if not os.path.exists(TOX_PIP_DIR): + subprocess.check_call([sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'install', '-t', TOX_PIP_DIR, + 'pip']) + shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0]) + # And use that version. + for n, a in enumerate(args): + if not a.startswith('-'): + if a in 'install' and '-e' in args[n:]: + args.insert(n + 1, '--no-use-pep517') + break + subprocess.check_call([sys.executable, os.path.join(TOX_PIP_DIR, 'pip')] + args) + + +if __name__ == '__main__': + pip(sys.argv[1:]) diff --git a/tox.ini b/tox.ini index e0eef95a..8b34c235 100644 --- a/tox.ini +++ b/tox.ini @@ -7,14 +7,16 @@ [tox] envlist=python +[helpers] +# Wrapper for calls to pip that make sure the version being used is a +# up-to-date, and to prevent the current working directory from being +# added to `sys.path`. +pip = python {toxinidir}/tools/tox_pip.py + [testenv] deps=-rtests/requirements.txt -# Changed from default (`python -m pip ...`) -# to prevent the current working directory -# from being added to `sys.path`. -install_command=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 --all +install_command = {[helpers]pip} install {opts} {packages} +list_dependencies_command = {[helpers]pip} freeze --all setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. -- cgit v1.2.3 From bf069fe9ddcadaa2c029067601d06a07d037d4f7 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 23 Aug 2019 23:07:52 +0200 Subject: setuptools: update vendored packaging --- setuptools/_vendor/packaging/__about__.py | 14 +- setuptools/_vendor/packaging/__init__.py | 20 +- setuptools/_vendor/packaging/_compat.py | 7 +- setuptools/_vendor/packaging/_structures.py | 4 +- setuptools/_vendor/packaging/markers.py | 91 +++--- setuptools/_vendor/packaging/requirements.py | 41 ++- setuptools/_vendor/packaging/specifiers.py | 71 ++--- setuptools/_vendor/packaging/tags.py | 404 +++++++++++++++++++++++++++ setuptools/_vendor/packaging/utils.py | 43 +++ setuptools/_vendor/packaging/version.py | 149 ++++++---- setuptools/_vendor/vendored.txt | 2 +- 11 files changed, 660 insertions(+), 186 deletions(-) create mode 100644 setuptools/_vendor/packaging/tags.py diff --git a/setuptools/_vendor/packaging/__about__.py b/setuptools/_vendor/packaging/__about__.py index 95d330ef..dc95138d 100644 --- a/setuptools/_vendor/packaging/__about__.py +++ b/setuptools/_vendor/packaging/__about__.py @@ -4,18 +4,24 @@ from __future__ import absolute_import, division, print_function __all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", ] __title__ = "packaging" __summary__ = "Core utilities for Python packages" __uri__ = "https://github.com/pypa/packaging" -__version__ = "16.8" +__version__ = "19.2" __author__ = "Donald Stufft and individual contributors" __email__ = "donald@stufft.io" __license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2016 %s" % __author__ +__copyright__ = "Copyright 2014-2019 %s" % __author__ diff --git a/setuptools/_vendor/packaging/__init__.py b/setuptools/_vendor/packaging/__init__.py index 5ee62202..a0cf67df 100644 --- a/setuptools/_vendor/packaging/__init__.py +++ b/setuptools/_vendor/packaging/__init__.py @@ -4,11 +4,23 @@ from __future__ import absolute_import, division, print_function from .__about__ import ( - __author__, __copyright__, __email__, __license__, __summary__, __title__, - __uri__, __version__ + __author__, + __copyright__, + __email__, + __license__, + __summary__, + __title__, + __uri__, + __version__, ) __all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", + "__title__", + "__summary__", + "__uri__", + "__version__", + "__author__", + "__email__", + "__license__", + "__copyright__", ] diff --git a/setuptools/_vendor/packaging/_compat.py b/setuptools/_vendor/packaging/_compat.py index 210bb80b..25da473c 100644 --- a/setuptools/_vendor/packaging/_compat.py +++ b/setuptools/_vendor/packaging/_compat.py @@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3 # flake8: noqa if PY3: - string_types = str, + string_types = (str,) else: - string_types = basestring, + string_types = (basestring,) def with_metaclass(meta, *bases): @@ -27,4 +27,5 @@ def with_metaclass(meta, *bases): class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) + + return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/setuptools/_vendor/packaging/_structures.py b/setuptools/_vendor/packaging/_structures.py index ccc27861..68dcca63 100644 --- a/setuptools/_vendor/packaging/_structures.py +++ b/setuptools/_vendor/packaging/_structures.py @@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function class Infinity(object): - def __repr__(self): return "Infinity" @@ -33,11 +32,11 @@ class Infinity(object): def __neg__(self): return NegativeInfinity + Infinity = Infinity() class NegativeInfinity(object): - def __repr__(self): return "-Infinity" @@ -65,4 +64,5 @@ class NegativeInfinity(object): def __neg__(self): return Infinity + NegativeInfinity = NegativeInfinity() diff --git a/setuptools/_vendor/packaging/markers.py b/setuptools/_vendor/packaging/markers.py index 031332a3..4bdfdb24 100644 --- a/setuptools/_vendor/packaging/markers.py +++ b/setuptools/_vendor/packaging/markers.py @@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier __all__ = [ - "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", - "Marker", "default_environment", + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", ] @@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError): class Node(object): - def __init__(self, value): self.value = value @@ -57,62 +59,52 @@ class Node(object): class Variable(Node): - def serialize(self): return str(self) class Value(Node): - def serialize(self): return '"{0}"'.format(self) class Op(Node): - def serialize(self): return str(self) VARIABLE = ( - L("implementation_version") | - L("platform_python_implementation") | - L("implementation_name") | - L("python_full_version") | - L("platform_release") | - L("platform_version") | - L("platform_machine") | - L("platform_system") | - L("python_version") | - L("sys_platform") | - L("os_name") | - L("os.name") | # PEP-345 - L("sys.platform") | # PEP-345 - L("platform.version") | # PEP-345 - L("platform.machine") | # PEP-345 - L("platform.python_implementation") | # PEP-345 - L("python_implementation") | # undocumented setuptools legacy - L("extra") + L("implementation_version") + | L("platform_python_implementation") + | L("implementation_name") + | L("python_full_version") + | L("platform_release") + | L("platform_version") + | L("platform_machine") + | L("platform_system") + | L("python_version") + | L("sys_platform") + | L("os_name") + | L("os.name") + | L("sys.platform") # PEP-345 + | L("platform.version") # PEP-345 + | L("platform.machine") # PEP-345 + | L("platform.python_implementation") # PEP-345 + | L("python_implementation") # PEP-345 + | L("extra") # undocumented setuptools legacy ) ALIASES = { - 'os.name': 'os_name', - 'sys.platform': 'sys_platform', - 'platform.version': 'platform_version', - 'platform.machine': 'platform_machine', - 'platform.python_implementation': 'platform_python_implementation', - 'python_implementation': 'platform_python_implementation' + "os.name": "os_name", + "sys.platform": "sys_platform", + "platform.version": "platform_version", + "platform.machine": "platform_machine", + "platform.python_implementation": "platform_python_implementation", + "python_implementation": "platform_python_implementation", } VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) VERSION_CMP = ( - L("===") | - L("==") | - L(">=") | - L("<=") | - L("!=") | - L("~=") | - L(">") | - L("<") + L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<") ) MARKER_OP = VERSION_CMP | L("not in") | L("in") @@ -152,8 +144,11 @@ def _format_marker(marker, first=True): # where the single item is itself it's own list. In that case we want skip # the rest of this function so that we don't get extraneous () on the # outside. - if (isinstance(marker, list) and len(marker) == 1 and - isinstance(marker[0], (list, tuple))): + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): return _format_marker(marker[0]) if isinstance(marker, list): @@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment): def format_full_version(info): - version = '{0.major}.{0.minor}.{0.micro}'.format(info) + version = "{0.major}.{0.minor}.{0.micro}".format(info) kind = info.releaselevel - if kind != 'final': + if kind != "final": version += kind[0] + str(info.serial) return version def default_environment(): - if hasattr(sys, 'implementation'): + if hasattr(sys, "implementation"): iver = format_full_version(sys.implementation.version) implementation_name = sys.implementation.name else: - iver = '0' - implementation_name = '' + iver = "0" + implementation_name = "" return { "implementation_name": implementation_name, @@ -264,19 +259,19 @@ def default_environment(): "platform_version": platform.version(), "python_full_version": platform.python_version(), "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], + "python_version": ".".join(platform.python_version_tuple()[:2]), "sys_platform": sys.platform, } class Marker(object): - def __init__(self, marker): try: self._markers = _coerce_parse_result(MARKER.parseString(marker)) except ParseException as e: err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc:e.loc + 8]) + marker, marker[e.loc : e.loc + 8] + ) raise InvalidMarker(err_str) def __str__(self): diff --git a/setuptools/_vendor/packaging/requirements.py b/setuptools/_vendor/packaging/requirements.py index 5b493416..8a0c2cb9 100644 --- a/setuptools/_vendor/packaging/requirements.py +++ b/setuptools/_vendor/packaging/requirements.py @@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) NAME = IDENTIFIER("name") EXTRA = IDENTIFIER -URI = Regex(r'[^ ]+')("url") -URL = (AT + URI) +URI = Regex(r"[^ ]+")("url") +URL = AT + URI EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") @@ -48,28 +48,31 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY -VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), - joinString=",", adjacent=False)("_raw_spec") +VERSION_MANY = Combine( + VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False +)("_raw_spec") _VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) -_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "") VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") MARKER_EXPR.setParseAction( - lambda s, l, t: Marker(s[t._original_start:t._original_end]) + lambda s, l, t: Marker(s[t._original_start : t._original_end]) ) -MARKER_SEPERATOR = SEMICOLON -MARKER = MARKER_SEPERATOR + MARKER_EXPR +MARKER_SEPARATOR = SEMICOLON +MARKER = MARKER_SEPARATOR + MARKER_EXPR VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) URL_AND_MARKER = URL + Optional(MARKER) -NAMED_REQUIREMENT = \ - NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) +NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd +# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see +# issue #104 +REQUIREMENT.parseString("x[]") class Requirement(object): @@ -90,15 +93,21 @@ class Requirement(object): req = REQUIREMENT.parseString(requirement_string) except ParseException as e: raise InvalidRequirement( - "Invalid requirement, parse error at \"{0!r}\"".format( - requirement_string[e.loc:e.loc + 8])) + 'Parse error at "{0!r}": {1}'.format( + requirement_string[e.loc : e.loc + 8], e.msg + ) + ) self.name = req.name if req.url: parsed_url = urlparse.urlparse(req.url) - if not (parsed_url.scheme and parsed_url.netloc) or ( - not parsed_url.scheme and not parsed_url.netloc): - raise InvalidRequirement("Invalid URL given") + if parsed_url.scheme == "file": + if urlparse.urlunparse(parsed_url) != req.url: + raise InvalidRequirement("Invalid URL given") + elif not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc + ): + raise InvalidRequirement("Invalid URL: {0}".format(req.url)) self.url = req.url else: self.url = None @@ -117,6 +126,8 @@ class Requirement(object): if self.url: parts.append("@ {0}".format(self.url)) + if self.marker: + parts.append(" ") if self.marker: parts.append("; {0}".format(self.marker)) diff --git a/setuptools/_vendor/packaging/specifiers.py b/setuptools/_vendor/packaging/specifiers.py index 7f5a76cf..743576a0 100644 --- a/setuptools/_vendor/packaging/specifiers.py +++ b/setuptools/_vendor/packaging/specifiers.py @@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError): class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): - @abc.abstractmethod def __str__(self): """ @@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier): if not match: raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - self._spec = ( - match.group("operator").strip(), - match.group("version").strip(), - ) + self._spec = (match.group("operator").strip(), match.group("version").strip()) # Store whether or not this Specifier should accept prereleases self._prereleases = prereleases @@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier): else "" ) - return "<{0}({1!r}{2})>".format( - self.__class__.__name__, - str(self), - pre, - ) + return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre) def __str__(self): return "{0}{1}".format(*self._spec) @@ -194,11 +186,12 @@ class _IndividualSpecifier(BaseSpecifier): # If our version is a prerelease, and we were not set to allow # prereleases, then we'll store it for later incase nothing # else matches this specifier. - if (parsed_version.is_prerelease and not - (prereleases or self.prereleases)): + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): found_prereleases.append(version) # Either this is not a prerelease, or we should have been - # accepting prereleases from the begining. + # accepting prereleases from the beginning. else: yielded = True yield version @@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier): class LegacySpecifier(_IndividualSpecifier): - _regex_str = ( - r""" + _regex_str = r""" (?P(==|!=|<=|>=|<|>)) \s* (?P @@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier): # them, and a comma since it's a version separator. ) """ - ) - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) _operators = { "==": "equal", @@ -269,13 +259,13 @@ def _require_version_compare(fn): if not isinstance(prospective, Version): return False return fn(self, prospective, spec) + return wrapped class Specifier(_IndividualSpecifier): - _regex_str = ( - r""" + _regex_str = r""" (?P(~=|==|!=|<=|>=|<|>|===)) (?P (?: @@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier): ) ) """ - ) - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) _operators = { "~=": "compatible", @@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier): prefix = ".".join( list( itertools.takewhile( - lambda x: (not x.startswith("post") and not - x.startswith("dev")), + lambda x: (not x.startswith("post") and not x.startswith("dev")), _version_split(spec), ) )[:-1] @@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier): # Add the prefix notation to the end of our string prefix += ".*" - return (self._get_operator(">=")(prospective, spec) and - self._get_operator("==")(prospective, prefix)) + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) @_require_version_compare def _compare_equal(self, prospective, spec): @@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier): # Shorten the prospective version to be the same length as the spec # so that we can determine if the specifier is a prefix of the # prospective version or not. - prospective = prospective[:len(spec)] + prospective = prospective[: len(spec)] # Pad out our two sides with zeros so that they both equal the same # length. @@ -503,7 +491,7 @@ class Specifier(_IndividualSpecifier): return False # Ensure that we do not allow a local version of the version mentioned - # in the specifier, which is techincally greater than, to match. + # in the specifier, which is technically greater than, to match. if prospective.local is not None: if Version(prospective.base_version) == Version(spec.base_version): return False @@ -567,27 +555,17 @@ def _pad_version(left, right): right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) # Get the rest of our versions - left_split.append(left[len(left_split[0]):]) - right_split.append(right[len(right_split[0]):]) + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) # Insert our padding - left_split.insert( - 1, - ["0"] * max(0, len(right_split[0]) - len(left_split[0])), - ) - right_split.insert( - 1, - ["0"] * max(0, len(left_split[0]) - len(right_split[0])), - ) + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) - return ( - list(itertools.chain(*left_split)), - list(itertools.chain(*right_split)), - ) + return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) class SpecifierSet(BaseSpecifier): - def __init__(self, specifiers="", prereleases=None): # Split on , to break each indidivual specifier into it's own item, and # strip each item to remove leading/trailing whitespace. @@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier): # given version is contained within all of them. # Note: This use of all() here means that an empty set of specifiers # will always return True, this is an explicit design decision. - return all( - s.contains(item, prereleases=prereleases) - for s in self._specs - ) + return all(s.contains(item, prereleases=prereleases) for s in self._specs) def filter(self, iterable, prereleases=None): # Determine if we're forcing a prerelease or not, if we're not forcing diff --git a/setuptools/_vendor/packaging/tags.py b/setuptools/_vendor/packaging/tags.py new file mode 100644 index 00000000..ec9942f0 --- /dev/null +++ b/setuptools/_vendor/packaging/tags.py @@ -0,0 +1,404 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import + +import distutils.util + +try: + from importlib.machinery import EXTENSION_SUFFIXES +except ImportError: # pragma: no cover + import imp + + EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()] + del imp +import platform +import re +import sys +import sysconfig +import warnings + + +INTERPRETER_SHORT_NAMES = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32 + + +class Tag(object): + + __slots__ = ["_interpreter", "_abi", "_platform"] + + def __init__(self, interpreter, abi, platform): + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + + @property + def interpreter(self): + return self._interpreter + + @property + def abi(self): + return self._abi + + @property + def platform(self): + return self._platform + + def __eq__(self, other): + return ( + (self.platform == other.platform) + and (self.abi == other.abi) + and (self.interpreter == other.interpreter) + ) + + def __hash__(self): + return hash((self._interpreter, self._abi, self._platform)) + + def __str__(self): + return "{}-{}-{}".format(self._interpreter, self._abi, self._platform) + + def __repr__(self): + return "<{self} @ {self_id}>".format(self=self, self_id=id(self)) + + +def parse_tag(tag): + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _normalize_string(string): + return string.replace(".", "_").replace("-", "_") + + +def _cpython_interpreter(py_version): + # TODO: Is using py_version_nodot for interpreter version critical? + return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1]) + + +def _cpython_abis(py_version): + abis = [] + version = "{}{}".format(*py_version[:2]) + debug = pymalloc = ucs4 = "" + with_debug = sysconfig.get_config_var("Py_DEBUG") + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version < (3, 8): + with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC") + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE") + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append("cp{version}".format(version=version)) + abis.insert( + 0, + "cp{version}{debug}{pymalloc}{ucs4}".format( + version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4 + ), + ) + return abis + + +def _cpython_tags(py_version, interpreter, abis, platforms): + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms): + yield tag + # PEP 384 was first implemented in Python 3.2. + for minor_version in range(py_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{major}{minor}".format( + major=py_version[0], minor=minor_version + ) + yield Tag(interpreter, "abi3", platform_) + + +def _pypy_interpreter(): + return "pp{py_major}{pypy_major}{pypy_minor}".format( + py_major=sys.version_info[0], + pypy_major=sys.pypy_version_info.major, + pypy_minor=sys.pypy_version_info.minor, + ) + + +def _generic_abi(): + abi = sysconfig.get_config_var("SOABI") + if abi: + return _normalize_string(abi) + else: + return "none" + + +def _pypy_tags(py_version, interpreter, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + for tag in (Tag(interpreter, "none", platform) for platform in platforms): + yield tag + + +def _generic_tags(interpreter, py_version, abi, platforms): + for tag in (Tag(interpreter, abi, platform) for platform in platforms): + yield tag + if abi != "none": + tags = (Tag(interpreter, "none", platform_) for platform_ in platforms) + for tag in tags: + yield tag + + +def _py_interpreter_range(py_version): + """ + Yield Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all following versions up to 'end'. + """ + yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1]) + yield "py{major}".format(major=py_version[0]) + for minor in range(py_version[1] - 1, -1, -1): + yield "py{major}{minor}".format(major=py_version[0], minor=minor) + + +def _independent_tags(interpreter, py_version, platforms): + """ + Return the sequence of tags that are consistent across implementations. + + The tags consist of: + - py*-none- + - -none-any + - py*-none-any + """ + for version in _py_interpreter_range(py_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(py_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER): + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version, cpu_arch): + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + formats.append("universal") + return formats + + +def _mac_platforms(version=None, arch=None): + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = tuple(map(int, version_str.split(".")[:2])) + if arch is None: + arch = _mac_arch(cpu_arch) + platforms = [] + for minor_version in range(version[1], -1, -1): + compat_version = version[0], minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + platforms.append( + "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + ) + return platforms + + +# From PEP 513. +def _is_manylinux_compatible(name, glibc_version): + # Check for presence of _manylinux module. + try: + import _manylinux + + return bool(getattr(_manylinux, name + "_compatible")) + except (ImportError, AttributeError): + # Fall through to heuristic check below. + pass + + return _have_compatible_glibc(*glibc_version) + + +def _glibc_version_string(): + # Returns glibc version string, or None if not using glibc. + import ctypes + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing. +def _check_glibc_version(version_str, required_major, minimum_minor): + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + "Expected glibc version with 2 components major.minor," + " got: %s" % version_str, + RuntimeWarning, + ) + return False + return ( + int(m.group("major")) == required_major + and int(m.group("minor")) >= minimum_minor + ) + + +def _have_compatible_glibc(required_major, minimum_minor): + version_str = _glibc_version_string() + if version_str is None: + return False + return _check_glibc_version(version_str, required_major, minimum_minor) + + +def _linux_platforms(is_32bit=_32_BIT_INTERPRETER): + linux = _normalize_string(distutils.util.get_platform()) + if linux == "linux_x86_64" and is_32bit: + linux = "linux_i686" + manylinux_support = ( + ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599) + ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571) + ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513) + ) + manylinux_support_iter = iter(manylinux_support) + for name, glibc_version in manylinux_support_iter: + if _is_manylinux_compatible(name, glibc_version): + platforms = [linux.replace("linux", name)] + break + else: + platforms = [] + # Support for a later manylinux implies support for an earlier version. + platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter] + platforms.append(linux) + return platforms + + +def _generic_platforms(): + platform = _normalize_string(distutils.util.get_platform()) + return [platform] + + +def _interpreter_name(): + name = platform.python_implementation().lower() + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def _generic_interpreter(name, py_version): + version = sysconfig.get_config_var("py_version_nodot") + if not version: + version = "".join(map(str, py_version[:2])) + return "{name}{version}".format(name=name, version=version) + + +def sys_tags(): + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + py_version = sys.version_info[:2] + interpreter_name = _interpreter_name() + if platform.system() == "Darwin": + platforms = _mac_platforms() + elif platform.system() == "Linux": + platforms = _linux_platforms() + else: + platforms = _generic_platforms() + + if interpreter_name == "cp": + interpreter = _cpython_interpreter(py_version) + abis = _cpython_abis(py_version) + for tag in _cpython_tags(py_version, interpreter, abis, platforms): + yield tag + elif interpreter_name == "pp": + interpreter = _pypy_interpreter() + abi = _generic_abi() + for tag in _pypy_tags(py_version, interpreter, abi, platforms): + yield tag + else: + interpreter = _generic_interpreter(interpreter_name, py_version) + abi = _generic_abi() + for tag in _generic_tags(interpreter, py_version, abi, platforms): + yield tag + for tag in _independent_tags(interpreter, py_version, platforms): + yield tag diff --git a/setuptools/_vendor/packaging/utils.py b/setuptools/_vendor/packaging/utils.py index 942387ce..88418786 100644 --- a/setuptools/_vendor/packaging/utils.py +++ b/setuptools/_vendor/packaging/utils.py @@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function import re +from .version import InvalidVersion, Version + _canonicalize_regex = re.compile(r"[-_.]+") @@ -12,3 +14,44 @@ _canonicalize_regex = re.compile(r"[-_.]+") def canonicalize_name(name): # This is taken from PEP 503. return _canonicalize_regex.sub("-", name).lower() + + +def canonicalize_version(version): + """ + This is very similar to Version.__str__, but has one subtle differences + with the way it handles the release segment. + """ + + try: + version = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + + parts = [] + + # Epoch + if version.epoch != 0: + parts.append("{0}!".format(version.epoch)) + + # Release segment + # NB: This strips trailing '.0's to normalize + parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release))) + + # Pre-release + if version.pre is not None: + parts.append("".join(str(x) for x in version.pre)) + + # Post-release + if version.post is not None: + parts.append(".post{0}".format(version.post)) + + # Development release + if version.dev is not None: + parts.append(".dev{0}".format(version.dev)) + + # Local version segment + if version.local is not None: + parts.append("+{0}".format(version.local)) + + return "".join(parts) diff --git a/setuptools/_vendor/packaging/version.py b/setuptools/_vendor/packaging/version.py index 83b5ee8c..95157a1f 100644 --- a/setuptools/_vendor/packaging/version.py +++ b/setuptools/_vendor/packaging/version.py @@ -10,14 +10,11 @@ import re from ._structures import Infinity -__all__ = [ - "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" -] +__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"] _Version = collections.namedtuple( - "_Version", - ["epoch", "release", "dev", "pre", "post", "local"], + "_Version", ["epoch", "release", "dev", "pre", "post", "local"] ) @@ -40,7 +37,6 @@ class InvalidVersion(ValueError): class _BaseVersion(object): - def __hash__(self): return hash(self._key) @@ -70,7 +66,6 @@ class _BaseVersion(object): class LegacyVersion(_BaseVersion): - def __init__(self, version): self._version = str(version) self._key = _legacy_cmpkey(self._version) @@ -89,6 +84,26 @@ class LegacyVersion(_BaseVersion): def base_version(self): return self._version + @property + def epoch(self): + return -1 + + @property + def release(self): + return None + + @property + def pre(self): + return None + + @property + def post(self): + return None + + @property + def dev(self): + return None + @property def local(self): return None @@ -101,13 +116,19 @@ class LegacyVersion(_BaseVersion): def is_postrelease(self): return False + @property + def is_devrelease(self): + return False -_legacy_version_component_re = re.compile( - r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, -) + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) _legacy_version_replacement_map = { - "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", } @@ -154,6 +175,7 @@ def _legacy_cmpkey(version): return epoch, parts + # Deliberately not anchored to the start and end of the string, to make it # easier for 3rd party code to reuse VERSION_PATTERN = r""" @@ -190,10 +212,7 @@ VERSION_PATTERN = r""" class Version(_BaseVersion): - _regex = re.compile( - r"^\s*" + VERSION_PATTERN + r"\s*$", - re.VERBOSE | re.IGNORECASE, - ) + _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE) def __init__(self, version): # Validate the version and parse it into pieces @@ -205,18 +224,11 @@ class Version(_BaseVersion): self._version = _Version( epoch=int(match.group("epoch")) if match.group("epoch") else 0, release=tuple(int(i) for i in match.group("release").split(".")), - pre=_parse_letter_version( - match.group("pre_l"), - match.group("pre_n"), - ), + pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")), post=_parse_letter_version( - match.group("post_l"), - match.group("post_n1") or match.group("post_n2"), - ), - dev=_parse_letter_version( - match.group("dev_l"), - match.group("dev_n"), + match.group("post_l"), match.group("post_n1") or match.group("post_n2") ), + dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")), local=_parse_local_version(match.group("local")), ) @@ -237,32 +249,57 @@ class Version(_BaseVersion): parts = [] # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) # Release segment - parts.append(".".join(str(x) for x in self._version.release)) + parts.append(".".join(str(x) for x in self.release)) # Pre-release - if self._version.pre is not None: - parts.append("".join(str(x) for x in self._version.pre)) + if self.pre is not None: + parts.append("".join(str(x) for x in self.pre)) # Post-release - if self._version.post is not None: - parts.append(".post{0}".format(self._version.post[1])) + if self.post is not None: + parts.append(".post{0}".format(self.post)) # Development release - if self._version.dev is not None: - parts.append(".dev{0}".format(self._version.dev[1])) + if self.dev is not None: + parts.append(".dev{0}".format(self.dev)) # Local version segment - if self._version.local is not None: - parts.append( - "+{0}".format(".".join(str(x) for x in self._version.local)) - ) + if self.local is not None: + parts.append("+{0}".format(self.local)) return "".join(parts) + @property + def epoch(self): + return self._version.epoch + + @property + def release(self): + return self._version.release + + @property + def pre(self): + return self._version.pre + + @property + def post(self): + return self._version.post[1] if self._version.post else None + + @property + def dev(self): + return self._version.dev[1] if self._version.dev else None + + @property + def local(self): + if self._version.local: + return ".".join(str(x) for x in self._version.local) + else: + return None + @property def public(self): return str(self).split("+", 1)[0] @@ -272,27 +309,25 @@ class Version(_BaseVersion): parts = [] # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) + if self.epoch != 0: + parts.append("{0}!".format(self.epoch)) # Release segment - parts.append(".".join(str(x) for x in self._version.release)) + parts.append(".".join(str(x) for x in self.release)) return "".join(parts) - @property - def local(self): - version_string = str(self) - if "+" in version_string: - return version_string.split("+", 1)[1] - @property def is_prerelease(self): - return bool(self._version.dev or self._version.pre) + return self.dev is not None or self.pre is not None @property def is_postrelease(self): - return bool(self._version.post) + return self.post is not None + + @property + def is_devrelease(self): + return self.dev is not None def _parse_letter_version(letter, number): @@ -326,7 +361,7 @@ def _parse_letter_version(letter, number): return letter, int(number) -_local_version_seperators = re.compile(r"[\._-]") +_local_version_separators = re.compile(r"[\._-]") def _parse_local_version(local): @@ -336,7 +371,7 @@ def _parse_local_version(local): if local is not None: return tuple( part.lower() if not part.isdigit() else int(part) - for part in _local_version_seperators.split(local) + for part in _local_version_separators.split(local) ) @@ -347,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local): # re-reverse it back into the correct order and make it a tuple and use # that for our sorting key. release = tuple( - reversed(list( - itertools.dropwhile( - lambda x: x == 0, - reversed(release), - ) - )) + reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))) ) # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. @@ -385,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local): # - Numeric segments sort numerically # - Shorter versions sort before longer versions when the prefixes # match exactly - local = tuple( - (i, "") if isinstance(i, int) else (-Infinity, i) - for i in local - ) + local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local) return epoch, release, pre, post, dev, local diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt index 5731b424..65183d9a 100644 --- a/setuptools/_vendor/vendored.txt +++ b/setuptools/_vendor/vendored.txt @@ -1,4 +1,4 @@ -packaging==16.8 +packaging==19.2 pyparsing==2.2.1 six==1.10.0 ordered-set==3.1.1 -- cgit v1.2.3 From 3d811b93a83d5931b821916c6ca172a69c403a97 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 23 Aug 2019 23:18:02 +0200 Subject: wheel: switch to `packaging.tags` for checking PEP 425 tags --- setuptools/glibc.py | 86 ---------- setuptools/pep425tags.py | 319 ------------------------------------ setuptools/tests/test_glibc.py | 52 ------ setuptools/tests/test_pep425tags.py | 170 ------------------- setuptools/wheel.py | 4 +- 5 files changed, 2 insertions(+), 629 deletions(-) delete mode 100644 setuptools/glibc.py delete mode 100644 setuptools/pep425tags.py delete mode 100644 setuptools/tests/test_glibc.py delete mode 100644 setuptools/tests/test_pep425tags.py diff --git a/setuptools/glibc.py b/setuptools/glibc.py deleted file mode 100644 index a134591c..00000000 --- a/setuptools/glibc.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/utils/glibc.py -from __future__ import absolute_import - -import ctypes -import re -import warnings - - -def glibc_version_string(): - "Returns glibc version string, or None if not using glibc." - - # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen - # manpage says, "If filename is NULL, then the returned handle is for the - # main program". This way we can let the linker do the work to figure out - # which libc our process is actually using. - process_namespace = ctypes.CDLL(None) - try: - gnu_get_libc_version = process_namespace.gnu_get_libc_version - except AttributeError: - # Symbol doesn't exist -> therefore, we are not linked to - # glibc. - return None - - # Call gnu_get_libc_version, which returns a string like "2.5" - gnu_get_libc_version.restype = ctypes.c_char_p - version_str = gnu_get_libc_version() - # py2 / py3 compatibility: - if not isinstance(version_str, str): - version_str = version_str.decode("ascii") - - return version_str - - -# Separated out from have_compatible_glibc for easier unit testing -def check_glibc_version(version_str, required_major, minimum_minor): - # Parse string and check against requested version. - # - # We use a regexp instead of str.split because we want to discard any - # random junk that might come after the minor version -- this might happen - # in patched/forked versions of glibc (e.g. Linaro's version of glibc - # uses version strings like "2.20-2014.11"). See gh-3588. - m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) - if not m: - warnings.warn("Expected glibc version with 2 components major.minor," - " got: %s" % version_str, RuntimeWarning) - return False - return (int(m.group("major")) == required_major and - int(m.group("minor")) >= minimum_minor) - - -def have_compatible_glibc(required_major, minimum_minor): - version_str = glibc_version_string() - if version_str is None: - return False - return check_glibc_version(version_str, required_major, minimum_minor) - - -# platform.libc_ver regularly returns completely nonsensical glibc -# versions. E.g. on my computer, platform says: -# -# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.7') -# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.9') -# -# But the truth is: -# -# ~$ ldd --version -# ldd (Debian GLIBC 2.22-11) 2.22 -# -# This is unfortunate, because it means that the linehaul data on libc -# versions that was generated by pip 8.1.2 and earlier is useless and -# misleading. Solution: instead of using platform, use our code that actually -# works. -def libc_ver(): - """Try to determine the glibc version - - Returns a tuple of strings (lib, version) which default to empty strings - in case the lookup fails. - """ - glibc_version = glibc_version_string() - if glibc_version is None: - return ("", "") - else: - return ("glibc", glibc_version) diff --git a/setuptools/pep425tags.py b/setuptools/pep425tags.py deleted file mode 100644 index 48745a29..00000000 --- a/setuptools/pep425tags.py +++ /dev/null @@ -1,319 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py -"""Generate and work with PEP 425 Compatibility Tags.""" -from __future__ import absolute_import - -import distutils.util -from distutils import log -import platform -import re -import sys -import sysconfig -import warnings -from collections import OrderedDict - -from .extern import six - -from . import glibc - -_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') - - -def get_config_var(var): - try: - return sysconfig.get_config_var(var) - except IOError as e: # Issue #1074 - warnings.warn("{}".format(e), RuntimeWarning) - return None - - -def get_abbr_impl(): - """Return abbreviated implementation name.""" - if hasattr(sys, 'pypy_version_info'): - pyimpl = 'pp' - elif sys.platform.startswith('java'): - pyimpl = 'jy' - elif sys.platform == 'cli': - pyimpl = 'ip' - else: - pyimpl = 'cp' - return pyimpl - - -def get_impl_ver(): - """Return implementation version.""" - impl_ver = get_config_var("py_version_nodot") - if not impl_ver or get_abbr_impl() == 'pp': - impl_ver = ''.join(map(str, get_impl_version_info())) - return impl_ver - - -def get_impl_version_info(): - """Return sys.version_info-like tuple for use in decrementing the minor - version.""" - if get_abbr_impl() == 'pp': - # as per https://github.com/pypa/pip/issues/2882 - return (sys.version_info[0], sys.pypy_version_info.major, - sys.pypy_version_info.minor) - else: - return sys.version_info[0], sys.version_info[1] - - -def get_impl_tag(): - """ - Returns the Tag for this specific implementation. - """ - return "{}{}".format(get_abbr_impl(), get_impl_ver()) - - -def get_flag(var, fallback, expected=True, warn=True): - """Use a fallback method for determining SOABI flags if the needed config - var is unset or unavailable.""" - val = get_config_var(var) - if val is None: - if warn: - log.debug("Config variable '%s' is unset, Python ABI tag may " - "be incorrect", var) - return fallback() - return val == expected - - -def get_abi_tag(): - """Return the ABI tag based on SOABI (if available) or emulate SOABI - (CPython 2, PyPy).""" - soabi = get_config_var('SOABI') - impl = get_abbr_impl() - if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): - d = '' - m = '' - u = '' - if get_flag('Py_DEBUG', - lambda: hasattr(sys, 'gettotalrefcount'), - warn=(impl == 'cp')): - d = 'd' - if get_flag('WITH_PYMALLOC', - lambda: impl == 'cp', - warn=(impl == 'cp')): - m = 'm' - if get_flag('Py_UNICODE_SIZE', - lambda: sys.maxunicode == 0x10ffff, - expected=4, - warn=(impl == 'cp' and - six.PY2)) \ - and six.PY2: - u = 'u' - abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) - elif soabi and soabi.startswith('cpython-'): - abi = 'cp' + soabi.split('-')[1] - elif soabi: - abi = soabi.replace('.', '_').replace('-', '_') - else: - abi = None - return abi - - -def _is_running_32bit(): - return sys.maxsize == 2147483647 - - -def get_platform(): - """Return our platform name 'win32', 'linux_x86_64'""" - if sys.platform == 'darwin': - # distutils.util.get_platform() returns the release based on the value - # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may - # be significantly older than the user's current machine. - release, _, machine = platform.mac_ver() - split_ver = release.split('.') - - if machine == "x86_64" and _is_running_32bit(): - machine = "i386" - elif machine == "ppc64" and _is_running_32bit(): - machine = "ppc" - - return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) - - # XXX remove distutils dependency - result = distutils.util.get_platform().replace('.', '_').replace('-', '_') - if result == "linux_x86_64" and _is_running_32bit(): - # 32 bit Python program (running on a 64 bit Linux): pip should only - # install and run 32 bit compiled extensions in that case. - result = "linux_i686" - - return result - - -def is_manylinux1_compatible(): - # Only Linux, and only x86-64 / i686 - if get_platform() not in {"linux_x86_64", "linux_i686"}: - return False - - # Check for presence of _manylinux module - try: - import _manylinux - return bool(_manylinux.manylinux1_compatible) - except (ImportError, AttributeError): - # Fall through to heuristic check below - pass - - # Check glibc version. CentOS 5 uses glibc 2.5. - return glibc.have_compatible_glibc(2, 5) - - -def get_darwin_arches(major, minor, machine): - """Return a list of supported arches (including group arches) for - the given major, minor and machine architecture of a macOS machine. - """ - arches = [] - - def _supports_arch(major, minor, arch): - # Looking at the application support for macOS versions in the chart - # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears - # our timeline looks roughly like: - # - # 10.0 - Introduces ppc support. - # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 - # and x86_64 support is CLI only, and cannot be used for GUI - # applications. - # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. - # 10.6 - Drops support for ppc64 - # 10.7 - Drops support for ppc - # - # Given that we do not know if we're installing a CLI or a GUI - # application, we must be conservative and assume it might be a GUI - # application and behave as if ppc64 and x86_64 support did not occur - # until 10.5. - # - # Note: The above information is taken from the "Application support" - # column in the chart not the "Processor support" since I believe - # that we care about what instruction sets an application can use - # not which processors the OS supports. - if arch == 'ppc': - return (major, minor) <= (10, 5) - if arch == 'ppc64': - return (major, minor) == (10, 5) - if arch == 'i386': - return (major, minor) >= (10, 4) - if arch == 'x86_64': - return (major, minor) >= (10, 5) - if arch in groups: - for garch in groups[arch]: - if _supports_arch(major, minor, garch): - return True - return False - - groups = OrderedDict([ - ("fat", ("i386", "ppc")), - ("intel", ("x86_64", "i386")), - ("fat64", ("x86_64", "ppc64")), - ("fat32", ("x86_64", "i386", "ppc")), - ]) - - if _supports_arch(major, minor, machine): - arches.append(machine) - - for garch in groups: - if machine in groups[garch] and _supports_arch(major, minor, garch): - arches.append(garch) - - arches.append('universal') - - return arches - - -def get_supported(versions=None, noarch=False, platform=None, - impl=None, abi=None): - """Return a list of supported tags for each version specified in - `versions`. - - :param versions: a list of string versions, of the form ["33", "32"], - or None. The first version will be assumed to support our ABI. - :param platform: specify the exact platform you want valid - tags for, or None. If None, use the local system platform. - :param impl: specify the exact implementation you want valid - tags for, or None. If None, use the local interpreter impl. - :param abi: specify the exact abi you want valid - tags for, or None. If None, use the local interpreter abi. - """ - supported = [] - - # Versions must be given with respect to the preference - if versions is None: - versions = [] - version_info = get_impl_version_info() - major = version_info[:-1] - # Support all previous minor Python versions. - for minor in range(version_info[-1], -1, -1): - versions.append(''.join(map(str, major + (minor,)))) - - impl = impl or get_abbr_impl() - - abis = [] - - abi = abi or get_abi_tag() - if abi: - abis[0:0] = [abi] - - abi3s = set() - import imp - for suffix in imp.get_suffixes(): - if suffix[0].startswith('.abi'): - abi3s.add(suffix[0].split('.', 2)[1]) - - abis.extend(sorted(list(abi3s))) - - abis.append('none') - - if not noarch: - arch = platform or get_platform() - if arch.startswith('macosx'): - # support macosx-10.6-intel on macosx-10.9-x86_64 - match = _osx_arch_pat.match(arch) - if match: - name, major, minor, actual_arch = match.groups() - tpl = '{}_{}_%i_%s'.format(name, major) - arches = [] - for m in reversed(range(int(minor) + 1)): - for a in get_darwin_arches(int(major), m, actual_arch): - arches.append(tpl % (m, a)) - else: - # arch pattern didn't match (?!) - arches = [arch] - elif platform is None and is_manylinux1_compatible(): - arches = [arch.replace('linux', 'manylinux1'), arch] - else: - arches = [arch] - - # Current version, current API (built specifically for our Python): - for abi in abis: - for arch in arches: - supported.append(('%s%s' % (impl, versions[0]), abi, arch)) - - # abi3 modules compatible with older version of Python - for version in versions[1:]: - # abi3 was introduced in Python 3.2 - if version in {'31', '30'}: - break - for abi in abi3s: # empty set if not Python 3 - for arch in arches: - supported.append(("%s%s" % (impl, version), abi, arch)) - - # Has binaries, does not use the Python API: - for arch in arches: - supported.append(('py%s' % (versions[0][0]), 'none', arch)) - - # No abi / arch, but requires our implementation: - supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) - # Tagged specifically as being cross-version compatible - # (with just the major version specified) - supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) - - # No abi / arch, generic Python - for i, version in enumerate(versions): - supported.append(('py%s' % (version,), 'none', 'any')) - if i == 0: - supported.append(('py%s' % (version[0]), 'none', 'any')) - - return supported - - -implementation_tag = get_impl_tag() diff --git a/setuptools/tests/test_glibc.py b/setuptools/tests/test_glibc.py deleted file mode 100644 index 795fdc56..00000000 --- a/setuptools/tests/test_glibc.py +++ /dev/null @@ -1,52 +0,0 @@ -import warnings - -import pytest - -from setuptools.glibc import check_glibc_version - -__metaclass__ = type - - -@pytest.fixture(params=[ - "2.20", - # used by "linaro glibc", see gh-3588 - "2.20-2014.11", - # weird possibilities that I just made up - "2.20+dev", - "2.20-custom", - "2.20.1", - ]) -def two_twenty(request): - return request.param - - -@pytest.fixture(params=["asdf", "", "foo.bar"]) -def bad_string(request): - return request.param - - -class TestGlibc: - def test_manylinux1_check_glibc_version(self, two_twenty): - """ - Test that the check_glibc_version function is robust against weird - glibc version strings. - """ - assert check_glibc_version(two_twenty, 2, 15) - assert check_glibc_version(two_twenty, 2, 20) - assert not check_glibc_version(two_twenty, 2, 21) - assert not check_glibc_version(two_twenty, 3, 15) - assert not check_glibc_version(two_twenty, 1, 15) - - def test_bad_versions(self, bad_string): - """ - For unparseable strings, warn and return False - """ - with warnings.catch_warnings(record=True) as ws: - warnings.filterwarnings("always") - assert not check_glibc_version(bad_string, 2, 5) - for w in ws: - if "Expected glibc version with" in str(w.message): - break - else: - # Didn't find the warning we were expecting - assert False diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py deleted file mode 100644 index 30afdec7..00000000 --- a/setuptools/tests/test_pep425tags.py +++ /dev/null @@ -1,170 +0,0 @@ -import sys - -import pytest -from mock import patch - -from setuptools import pep425tags - -__metaclass__ = type - - -class TestPEP425Tags: - - def mock_get_config_var(self, **kwd): - """ - Patch sysconfig.get_config_var for arbitrary keys. - """ - get_config_var = pep425tags.sysconfig.get_config_var - - def _mock_get_config_var(var): - if var in kwd: - return kwd[var] - return get_config_var(var) - return _mock_get_config_var - - def abi_tag_unicode(self, flags, config_vars): - """ - Used to test ABI tags, verify correct use of the `u` flag - """ - config_vars.update({'SOABI': None}) - base = pep425tags.get_abbr_impl() + pep425tags.get_impl_ver() - - if sys.version_info < (3, 3): - config_vars.update({'Py_UNICODE_SIZE': 2}) - mock_gcf = self.mock_get_config_var(**config_vars) - with patch( - 'setuptools.pep425tags.sysconfig.get_config_var', - mock_gcf): - abi_tag = pep425tags.get_abi_tag() - assert abi_tag == base + flags - - config_vars.update({'Py_UNICODE_SIZE': 4}) - mock_gcf = self.mock_get_config_var(**config_vars) - with patch('setuptools.pep425tags.sysconfig.get_config_var', - mock_gcf): - abi_tag = pep425tags.get_abi_tag() - assert abi_tag == base + flags + 'u' - - else: - # On Python >= 3.3, UCS-4 is essentially permanently enabled, and - # Py_UNICODE_SIZE is None. SOABI on these builds does not include - # the 'u' so manual SOABI detection should not do so either. - config_vars.update({'Py_UNICODE_SIZE': None}) - mock_gcf = self.mock_get_config_var(**config_vars) - with patch('setuptools.pep425tags.sysconfig.get_config_var', - mock_gcf): - abi_tag = pep425tags.get_abi_tag() - assert abi_tag == base + flags - - def test_broken_sysconfig(self): - """ - Test that pep425tags still works when sysconfig is broken. - Can be a problem on Python 2.7 - Issue #1074. - """ - def raises_ioerror(var): - raise IOError("I have the wrong path!") - - with patch('setuptools.pep425tags.sysconfig.get_config_var', - raises_ioerror): - with pytest.warns(RuntimeWarning): - assert len(pep425tags.get_supported()) - - def test_no_hyphen_tag(self): - """ - Test that no tag contains a hyphen. - """ - mock_gcf = self.mock_get_config_var(SOABI='cpython-35m-darwin') - - with patch('setuptools.pep425tags.sysconfig.get_config_var', - mock_gcf): - supported = pep425tags.get_supported() - - for (py, abi, plat) in supported: - assert '-' not in py - assert '-' not in abi - assert '-' not in plat - - def test_manual_abi_noflags(self): - """ - Test that no flags are set on a non-PyDebug, non-Pymalloc ABI tag. - """ - self.abi_tag_unicode('', {'Py_DEBUG': False, 'WITH_PYMALLOC': False}) - - def test_manual_abi_d_flag(self): - """ - Test that the `d` flag is set on a PyDebug, non-Pymalloc ABI tag. - """ - self.abi_tag_unicode('d', {'Py_DEBUG': True, 'WITH_PYMALLOC': False}) - - def test_manual_abi_m_flag(self): - """ - Test that the `m` flag is set on a non-PyDebug, Pymalloc ABI tag. - """ - self.abi_tag_unicode('m', {'Py_DEBUG': False, 'WITH_PYMALLOC': True}) - - def test_manual_abi_dm_flags(self): - """ - Test that the `dm` flags are set on a PyDebug, Pymalloc ABI tag. - """ - self.abi_tag_unicode('dm', {'Py_DEBUG': True, 'WITH_PYMALLOC': True}) - - -class TestManylinux1Tags: - - @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64') - @patch('setuptools.glibc.have_compatible_glibc', - lambda major, minor: True) - def test_manylinux1_compatible_on_linux_x86_64(self): - """ - Test that manylinux1 is enabled on linux_x86_64 - """ - assert pep425tags.is_manylinux1_compatible() - - @patch('setuptools.pep425tags.get_platform', lambda: 'linux_i686') - @patch('setuptools.glibc.have_compatible_glibc', - lambda major, minor: True) - def test_manylinux1_compatible_on_linux_i686(self): - """ - Test that manylinux1 is enabled on linux_i686 - """ - assert pep425tags.is_manylinux1_compatible() - - @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64') - @patch('setuptools.glibc.have_compatible_glibc', - lambda major, minor: False) - def test_manylinux1_2(self): - """ - Test that manylinux1 is disabled with incompatible glibc - """ - assert not pep425tags.is_manylinux1_compatible() - - @patch('setuptools.pep425tags.get_platform', lambda: 'arm6vl') - @patch('setuptools.glibc.have_compatible_glibc', - lambda major, minor: True) - def test_manylinux1_3(self): - """ - Test that manylinux1 is disabled on arm6vl - """ - assert not pep425tags.is_manylinux1_compatible() - - @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64') - @patch('setuptools.glibc.have_compatible_glibc', - lambda major, minor: True) - @patch('sys.platform', 'linux2') - def test_manylinux1_tag_is_first(self): - """ - Test that the more specific tag manylinux1 comes first. - """ - groups = {} - for pyimpl, abi, arch in pep425tags.get_supported(): - groups.setdefault((pyimpl, abi), []).append(arch) - - for arches in groups.values(): - if arches == ['any']: - continue - # Expect the most specific arch first: - if len(arches) == 3: - assert arches == ['manylinux1_x86_64', 'linux_x86_64', 'any'] - else: - assert arches == ['manylinux1_x86_64', 'linux_x86_64'] diff --git a/setuptools/wheel.py b/setuptools/wheel.py index e11f0a1d..502f8410 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -11,9 +11,9 @@ import zipfile import pkg_resources import setuptools from pkg_resources import parse_version +from setuptools.extern.packaging.tags import sys_tags from setuptools.extern.packaging.utils import canonicalize_name from setuptools.extern.six import PY3 -from setuptools import pep425tags from setuptools.command.egg_info import write_requirements @@ -76,7 +76,7 @@ class Wheel: def is_compatible(self): '''Is the wheel is compatible with the current platform?''' - supported_tags = pep425tags.get_supported() + supported_tags = set(map(str, sys_tags())) return next((True for t in self.tags() if t in supported_tags), False) def egg_name(self): -- cgit v1.2.3 From 0744f7b410ff937cec428b7d6ca103c419d81661 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 23 Aug 2019 23:57:35 +0200 Subject: add changelog entry --- changelog.d/1829.change.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/1829.change.rst diff --git a/changelog.d/1829.change.rst b/changelog.d/1829.change.rst new file mode 100644 index 00000000..36be832a --- /dev/null +++ b/changelog.d/1829.change.rst @@ -0,0 +1,3 @@ +Update handling of wheels compatibility tags: +* add support for manylinux2010 +* fix use of removed 'm' ABI flag in Python 3.8 on Windows -- cgit v1.2.3 From b5a9209cd2a3882204e0f9c9158a4a4d1ebf5e9b Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 14 Aug 2019 02:01:37 +0200 Subject: docs: drop ez_setup documentation and related references --- changelog.d/1862.doc.rst | 1 + docs/easy_install.txt | 4 +- docs/ez_setup.txt | 195 ----------------------------------------------- docs/setuptools.txt | 29 +------ 4 files changed, 4 insertions(+), 225 deletions(-) create mode 100644 changelog.d/1862.doc.rst delete mode 100644 docs/ez_setup.txt diff --git a/changelog.d/1862.doc.rst b/changelog.d/1862.doc.rst new file mode 100644 index 00000000..b71583ba --- /dev/null +++ b/changelog.d/1862.doc.rst @@ -0,0 +1 @@ +Drop ez_setup documentation: deprecated for some time (last updated in 2016), and still relying on easy_install (deprecated too). diff --git a/docs/easy_install.txt b/docs/easy_install.txt index aa11f890..e247d8fd 100644 --- a/docs/easy_install.txt +++ b/docs/easy_install.txt @@ -317,8 +317,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts directory, you can also retarget the installation location for scripts so they go on a directory that's already on the ``PATH``. For more information see `Command-Line Options`_ and `Configuration Files`_. During installation, -pass command line options (such as ``--script-dir``) to -``ez_setup.py`` to control where ``easy_install.exe`` will be installed. +pass command line options (such as ``--script-dir``) to to control where +scripts will be installed. Windows Executable Launcher diff --git a/docs/ez_setup.txt b/docs/ez_setup.txt deleted file mode 100644 index 0126fee3..00000000 --- a/docs/ez_setup.txt +++ /dev/null @@ -1,195 +0,0 @@ -:orphan: - -``ez_setup`` distribution guide -=============================== - -Using ``setuptools``... Without bundling it! ---------------------------------------------- - -.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support. - -.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py - -.. _EasyInstall Installation Instructions: easy_install.html - -.. _Custom Installation Locations: easy_install.html - -Your users might not have ``setuptools`` installed on their machines, or even -if they do, it might not be the right version. Fixing this is easy; just -download `ez_setup.py`_, and put it in the same directory as your ``setup.py`` -script. (Be sure to add it to your revision control system, too.) Then add -these two lines to the very top of your setup script, before the script imports -anything from setuptools: - -.. code-block:: python - - import ez_setup - ez_setup.use_setuptools() - -That's it. The ``ez_setup`` module will automatically download a matching -version of ``setuptools`` from PyPI, if it isn't present on the target system. -Whenever you install an updated version of setuptools, you should also update -your projects' ``ez_setup.py`` files, so that a matching version gets installed -on the target machine(s). - -(By the way, if you need to distribute a specific version of ``setuptools``, -you can specify the exact version and base download URL as parameters to the -``use_setuptools()`` function. See the function's docstring for details.) - - -What Your Users Should Know ---------------------------- - -In general, a setuptools-based project looks just like any distutils-based -project -- as long as your users have an internet connection and are installing -to ``site-packages``, that is. But for some users, these conditions don't -apply, and they may become frustrated if this is their first encounter with -a setuptools-based project. To keep these users happy, you should review the -following topics in your project's installation instructions, if they are -relevant to your project and your target audience isn't already familiar with -setuptools and ``easy_install``. - -Network Access - If your project is using ``ez_setup``, you should inform users of the - need to either have network access, or to preinstall the correct version of - setuptools using the `EasyInstall installation instructions`_. Those - instructions also have tips for dealing with firewalls as well as how to - manually download and install setuptools. - -Custom Installation Locations - You should inform your users that if they are installing your project to - somewhere other than the main ``site-packages`` directory, they should - first install setuptools using the instructions for `Custom Installation - Locations`_, before installing your project. - -Your Project's Dependencies - If your project depends on other projects that may need to be downloaded - from PyPI or elsewhere, you should list them in your installation - instructions, or tell users how to find out what they are. While most - users will not need this information, any users who don't have unrestricted - internet access may have to find, download, and install the other projects - manually. (Note, however, that they must still install those projects - using ``easy_install``, or your project will not know they are installed, - and your setup script will try to download them again.) - - If you want to be especially friendly to users with limited network access, - you may wish to build eggs for your project and its dependencies, making - them all available for download from your site, or at least create a page - with links to all of the needed eggs. In this way, users with limited - network access can manually download all the eggs to a single directory, - then use the ``-f`` option of ``easy_install`` to specify the directory - to find eggs in. Users who have full network access can just use ``-f`` - with the URL of your download page, and ``easy_install`` will find all the - needed eggs using your links directly. This is also useful when your - target audience isn't able to compile packages (e.g. most Windows users) - and your package or some of its dependencies include C code. - -Revision Control System Users and Co-Developers - Users and co-developers who are tracking your in-development code using - a revision control system should probably read this manual's sections - regarding such development. Alternately, you may wish to create a - quick-reference guide containing the tips from this manual that apply to - your particular situation. For example, if you recommend that people use - ``setup.py develop`` when tracking your in-development code, you should let - them know that this needs to be run after every update or commit. - - Similarly, if you remove modules or data files from your project, you - should remind them to run ``setup.py clean --all`` and delete any obsolete - ``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not - just setuptools, but not everybody knows about them; be kind to your users - by spelling out your project's best practices rather than leaving them - guessing.) - -Creating System Packages - Some users want to manage all Python packages using a single package - manager, and sometimes that package manager isn't ``easy_install``! - Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and - ``bdist_dumb`` formats for system packaging. If a user has a locally- - installed "bdist" packaging tool that internally uses the distutils - ``install`` command, it should be able to work with ``setuptools``. Some - examples of "bdist" formats that this should work with include the - ``bdist_nsi`` and ``bdist_msi`` formats for Windows. - - However, packaging tools that build binary distributions by running - ``setup.py install`` on the command line or as a subprocess will require - modification to work with setuptools. They should use the - ``--single-version-externally-managed`` option to the ``install`` command, - combined with the standard ``--root`` or ``--record`` options. - See the `install command`_ documentation below for more details. The - ``bdist_deb`` command is an example of a command that currently requires - this kind of patching to work with setuptools. - - Please note that building system packages may require you to install - some system software, for example ``bdist_rpm`` requires the ``rpmbuild`` - command to be installed. - - If you or your users have a problem building a usable system package for - your project, please report the problem via the mailing list so that - either the "bdist" tool in question or setuptools can be modified to - resolve the issue. - -Your users might not have ``setuptools`` installed on their machines, or even -if they do, it might not be the right version. Fixing this is easy; just -download `ez_setup.py`_, and put it in the same directory as your ``setup.py`` -script. (Be sure to add it to your revision control system, too.) Then add -these two lines to the very top of your setup script, before the script imports -anything from setuptools: - -.. code-block:: python - - import ez_setup - ez_setup.use_setuptools() - -That's it. The ``ez_setup`` module will automatically download a matching -version of ``setuptools`` from PyPI, if it isn't present on the target system. -Whenever you install an updated version of setuptools, you should also update -your projects' ``ez_setup.py`` files, so that a matching version gets installed -on the target machine(s). - -(By the way, if you need to distribute a specific version of ``setuptools``, -you can specify the exact version and base download URL as parameters to the -``use_setuptools()`` function. See the function's docstring for details.) - -.. _install command: - -``install`` - Run ``easy_install`` or old-style installation -============================================================ - -The setuptools ``install`` command is basically a shortcut to run the -``easy_install`` command on the current project. However, for convenience -in creating "system packages" of setuptools-based projects, you can also -use this option: - -``--single-version-externally-managed`` - This boolean option tells the ``install`` command to perform an "old style" - installation, with the addition of an ``.egg-info`` directory so that the - installed project will still have its metadata available and operate - normally. If you use this option, you *must* also specify the ``--root`` - or ``--record`` options (or both), because otherwise you will have no way - to identify and remove the installed files. - -This option is automatically in effect when ``install`` is invoked by another -distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm`` -will create system packages of eggs. It is also automatically in effect if -you specify the ``--root`` option. - - -``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages`` -============================================================================== - -Setuptools runs this command as part of ``install`` operations that use the -``--single-version-externally-managed`` options. You should not invoke it -directly; it is documented here for completeness and so that distutils -extensions such as system package builders can make use of it. This command -has only one option: - -``--install-dir=DIR, -d DIR`` - The parent directory where the ``.egg-info`` directory will be placed. - Defaults to the same as the ``--install-dir`` option specified for the - ``install_lib`` command, which is usually the system ``site-packages`` - directory. - -This command assumes that the ``egg_info`` command has been given valid options -via the command line or ``setup.cfg``, as it will invoke the ``egg_info`` -command and use its options to locate the project's source ``.egg-info`` -directory. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 26a3044e..ba6b170a 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -8,14 +8,7 @@ distribute Python packages, especially ones that have dependencies on other packages. Packages built and distributed using ``setuptools`` look to the user like -ordinary Python packages based on the ``distutils``. Your users don't need to -install or even know about setuptools in order to use them, and you don't -have to include the entire setuptools package in your distributions. By -including just a single `bootstrap module`_ (a 12K .py file), your package will -automatically download and install ``setuptools`` if the user is building your -package from source and doesn't have a suitable version already installed. - -.. _bootstrap module: https://bootstrap.pypa.io/ez_setup.py +ordinary Python packages based on the ``distutils``. Feature Highlights: @@ -62,8 +55,6 @@ Feature Highlights: .. contents:: **Table of Contents** -.. _ez_setup.py: `bootstrap module`_ - ----------------- Developer's Guide @@ -596,10 +587,6 @@ Python must be available via the ``PATH`` environment variable, under its "long" name. That is, if the egg is built for Python 2.3, there must be a ``python2.3`` executable present in a directory on ``PATH``. -This feature is primarily intended to support ez_setup the installation of -setuptools itself on non-Windows platforms, but may also be useful for other -projects as well. - IMPORTANT NOTE: Eggs with an "eggsecutable" header cannot be renamed, or invoked via symlinks. They *must* be invoked using their original filename, in order to ensure that, once running, ``pkg_resources`` will know what project @@ -1263,20 +1250,6 @@ To install your newly uploaded package ``example_pkg``, you can use pip:: If you have issues at any point, please refer to `Packaging project tutorials`_ for clarification. -Distributing legacy ``setuptools`` projects using ez_setup.py -------------------------------------------------------------- - -.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support. - -Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is -deprecated in favor of PIP. Please consider migrating to using pip and twine based -distribution. - -However, if you still have any ``ez_setup`` based packages, documentation for -ez_setup based distributions can be found at `ez_setup distribution guide`_. - -.. _ez_setup distribution guide: ez_setup.html - Setting the ``zip_safe`` flag ----------------------------- -- cgit v1.2.3 From 6a73945462a7e73168be53e377e4110a5dc89fb9 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Wed, 14 Aug 2019 00:49:50 +0200 Subject: tests: drop (easy_install based) manual tests --- tests/manual_test.py | 100 --------------------------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 tests/manual_test.py diff --git a/tests/manual_test.py b/tests/manual_test.py deleted file mode 100644 index 99db4b01..00000000 --- a/tests/manual_test.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python - -import sys -import os -import shutil -import tempfile -import subprocess -from distutils.command.install import INSTALL_SCHEMES -from string import Template - -from setuptools.extern.six.moves import urllib - - -def _system_call(*args): - assert subprocess.call(args) == 0 - - -def tempdir(func): - def _tempdir(*args, **kwargs): - test_dir = tempfile.mkdtemp() - old_dir = os.getcwd() - os.chdir(test_dir) - try: - return func(*args, **kwargs) - finally: - os.chdir(old_dir) - shutil.rmtree(test_dir) - - return _tempdir - - -SIMPLE_BUILDOUT = """\ -[buildout] - -parts = eggs - -[eggs] -recipe = zc.recipe.egg - -eggs = - extensions -""" - -BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py' -PYVER = sys.version.split()[0][:3] - -_VARS = {'base': '.', - 'py_version_short': PYVER} - -scheme = 'nt' if sys.platform == 'win32' else 'unix_prefix' -PURELIB = INSTALL_SCHEMES[scheme]['purelib'] - - -@tempdir -def test_virtualenv(): - """virtualenv with setuptools""" - purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS)) - _system_call('virtualenv', '--no-site-packages', '.') - _system_call('bin/easy_install', 'setuptools==dev') - # linux specific - site_pkg = os.listdir(purelib) - site_pkg.sort() - assert 'setuptools' in site_pkg[0] - easy_install = os.path.join(purelib, 'easy-install.pth') - with open(easy_install) as f: - res = f.read() - assert 'setuptools' in res - - -@tempdir -def test_full(): - """virtualenv + pip + buildout""" - _system_call('virtualenv', '--no-site-packages', '.') - _system_call('bin/easy_install', '-q', 'setuptools==dev') - _system_call('bin/easy_install', '-qU', 'setuptools==dev') - _system_call('bin/easy_install', '-q', 'pip') - _system_call('bin/pip', 'install', '-q', 'zc.buildout') - - with open('buildout.cfg', 'w') as f: - f.write(SIMPLE_BUILDOUT) - - with open('bootstrap.py', 'w') as f: - f.write(urllib.request.urlopen(BOOTSTRAP).read()) - - _system_call('bin/python', 'bootstrap.py') - _system_call('bin/buildout', '-q') - eggs = os.listdir('eggs') - eggs.sort() - assert len(eggs) == 3 - assert eggs[1].startswith('setuptools') - del eggs[1] - assert eggs == [ - 'extensions-0.3-py2.6.egg', - 'zc.recipe.egg-1.2.2-py2.6.egg', - ] - - -if __name__ == '__main__': - test_virtualenv() - test_full() -- cgit v1.2.3 From 4c0e204413e6d2c33662a02c73a0ad13d81af9e5 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 8 Oct 2019 19:32:09 +0200 Subject: doc: drop most references to EasyInstall --- changelog.d/1868.doc.rst | 1 + docs/development.txt | 2 +- docs/formats.txt | 7 +- docs/pkg_resources.txt | 14 ++-- docs/setuptools.txt | 178 ++++++++++++----------------------------------- 5 files changed, 54 insertions(+), 148 deletions(-) create mode 100644 changelog.d/1868.doc.rst diff --git a/changelog.d/1868.doc.rst b/changelog.d/1868.doc.rst new file mode 100644 index 00000000..82283d7a --- /dev/null +++ b/changelog.d/1868.doc.rst @@ -0,0 +1 @@ +Drop most documentation references to (deprecated) EasyInstall. diff --git a/docs/development.txt b/docs/development.txt index 455f038a..28e653fe 100644 --- a/docs/development.txt +++ b/docs/development.txt @@ -7,7 +7,7 @@ Authority (PyPA) and led by Jason R. Coombs. This document describes the process by which Setuptools is developed. This document assumes the reader has some passing familiarity with -*using* setuptools, the ``pkg_resources`` module, and EasyInstall. It +*using* setuptools, the ``pkg_resources`` module, and pip. It does not attempt to explain basic concepts like inter-project dependencies, nor does it contain detailed lexical syntax for most file formats. Neither does it explain concepts like "namespace diff --git a/docs/formats.txt b/docs/formats.txt index a182eb99..6c0456de 100644 --- a/docs/formats.txt +++ b/docs/formats.txt @@ -299,11 +299,8 @@ specified by the ``setup_requires`` parameter to the Distribution. A list of dependency URLs, one per line, as specified using the ``dependency_links`` keyword to ``setup()``. These may be direct download URLs, or the URLs of web pages containing direct download -links, and will be used by EasyInstall to find dependencies, as though -the user had manually provided them via the ``--find-links`` command -line option. Please see the setuptools manual and EasyInstall manual -for more information on specifying this option, and for information on -how EasyInstall processes ``--find-links`` URLs. +links. Please see the setuptools manual for more information on +specifying this option. ``depends.txt`` -- Obsolete, do not create! diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt index 806f1b14..b887a923 100644 --- a/docs/pkg_resources.txt +++ b/docs/pkg_resources.txt @@ -245,8 +245,8 @@ abbreviation for ``pkg_resources.working_set.require()``: interactive interpreter hacking than for production use. If you're creating an actual library or application, it's strongly recommended that you create a "setup.py" script using ``setuptools``, and declare all your requirements - there. That way, tools like EasyInstall can automatically detect what - requirements your package has, and deal with them accordingly. + there. That way, tools like pip can automatically detect what requirements + your package has, and deal with them accordingly. Note that calling ``require('SomePackage')`` will not install ``SomePackage`` if it isn't already present. If you need to do this, you @@ -611,9 +611,9 @@ Requirements Parsing or activation of both Report-O-Rama and any libraries it needs in order to provide PDF support. For example, you could use:: - easy_install.py Report-O-Rama[PDF] + pip install Report-O-Rama[PDF] - To install the necessary packages using the EasyInstall program, or call + To install the necessary packages using pip, or call ``pkg_resources.require('Report-O-Rama[PDF]')`` to add the necessary distributions to sys.path at runtime. @@ -1843,9 +1843,9 @@ History because it isn't necessarily a filesystem path (and hasn't been for some time now). The ``location`` of ``Distribution`` objects in the filesystem should always be normalized using ``pkg_resources.normalize_path()``; all - of the setuptools and EasyInstall code that generates distributions from - the filesystem (including ``Distribution.from_filename()``) ensure this - invariant, but if you use a more generic API like ``Distribution()`` or + of the setuptools' code that generates distributions from the filesystem + (including ``Distribution.from_filename()``) ensure this invariant, but if + you use a more generic API like ``Distribution()`` or ``Distribution.from_location()`` you should take care that you don't create a distribution with an un-normalized filesystem path. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 26a3044e..8468c3ad 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -19,12 +19,6 @@ package from source and doesn't have a suitable version already installed. Feature Highlights: -* Automatically find/download/install/upgrade dependencies at build time using - the `EasyInstall tool `_, - which supports downloading via HTTP, FTP, Subversion, and SourceForge, and - automatically scans web pages linked from PyPI to find download links. (It's - the closest thing to CPAN currently available for Python.) - * Create `Python Eggs `_ - a single-file importable distribution format @@ -73,10 +67,6 @@ Developer's Guide Installing ``setuptools`` ========================= -.. _EasyInstall Installation Instructions: easy_install.html - -.. _Custom Installation Locations: easy_install.html - .. _Installing Packages: https://packaging.python.org/tutorials/installing-packages/ To install the latest version of setuptools, use:: @@ -160,7 +150,7 @@ Specifying Your Project's Version Setuptools can work well with most versioning schemes; there are, however, a few special things to watch out for, in order to ensure that setuptools and -EasyInstall can always tell what version of your package is newer than another +other tools can always tell what version of your package is newer than another version. Knowing these things will also help you correctly specify what versions of other projects your project depends on. @@ -301,11 +291,10 @@ unless you need the associated ``setuptools`` feature. ``setup_requires`` A string or list of strings specifying what other distributions need to be present in order for the *setup script* to run. ``setuptools`` will - attempt to obtain these (even going so far as to download them using - ``EasyInstall``) before processing the rest of the setup script or commands. - This argument is needed if you are using distutils extensions as part of - your build process; for example, extensions that process setup() arguments - and turn them into EGG-INFO metadata files. + attempt to obtain these before processing the rest of the setup script or + commands. This argument is needed if you are using distutils extensions as + part of your build process; for example, extensions that process setup() + arguments and turn them into EGG-INFO metadata files. (Note: projects listed in ``setup_requires`` will NOT be automatically installed on the system where the setup script is being run. They are @@ -318,8 +307,7 @@ unless you need the associated ``setuptools`` feature. A list of strings naming URLs to be searched when satisfying dependencies. These links will be used if needed to install packages specified by ``setup_requires`` or ``tests_require``. They will also be written into - the egg's metadata for use by tools like EasyInstall to use when installing - an ``.egg`` file. + the egg's metadata for use during install by tools that support them. ``namespace_packages`` A list of strings naming the project's "namespace packages". A namespace @@ -351,8 +339,7 @@ unless you need the associated ``setuptools`` feature. needed to install it, you can use this option to specify them. It should be a string or list of strings specifying what other distributions need to be present for the package's tests to run. When you run the ``test`` - command, ``setuptools`` will attempt to obtain these (even going - so far as to download them using ``EasyInstall``). Note that these + command, ``setuptools`` will attempt to obtain these. Note that these required projects will *not* be installed on the system where the tests are run, but only downloaded to the project's setup directory if they're not already installed locally. @@ -552,11 +539,12 @@ script called ``baz``, you might do something like this:: ) When this project is installed on non-Windows platforms (using "setup.py -install", "setup.py develop", or by using EasyInstall), a set of ``foo``, -``bar``, and ``baz`` scripts will be installed that import ``main_func`` and -``some_func`` from the specified modules. The functions you specify are called -with no arguments, and their return value is passed to ``sys.exit()``, so you -can return an errorlevel or message to print to stderr. +install", "setup.py develop", or with pip), a set of ``foo``, ``bar``, +and ``baz`` scripts will be installed that import ``main_func`` and +``some_func`` from the specified modules. The functions you specify are +called with no arguments, and their return value is passed to +``sys.exit()``, so you can return an errorlevel or message to print to +stderr. On Windows, a set of ``foo.exe``, ``bar.exe``, and ``baz.exe`` launchers are created, alongside a set of ``foo.py``, ``bar.py``, and ``baz.pyw`` files. The @@ -613,7 +601,7 @@ Declaring Dependencies ``setuptools`` supports automatically installing dependencies when a package is installed, and including information about dependencies in Python Eggs (so that -package management tools like EasyInstall can use the information). +package management tools like pip can use the information). ``setuptools`` and ``pkg_resources`` use a common syntax for specifying a project's required dependencies. This syntax consists of a project's PyPI @@ -652,10 +640,9 @@ requirement in a string, each requirement must begin on a new line. This has three effects: -1. When your project is installed, either by using EasyInstall, ``setup.py - install``, or ``setup.py develop``, all of the dependencies not already - installed will be located (via PyPI), downloaded, built (if necessary), - and installed. +1. When your project is installed, either by using pip, ``setup.py install``, + or ``setup.py develop``, all of the dependencies not already installed will + be located (via PyPI), downloaded, built (if necessary), and installed. 2. Any scripts in your project will be installed with wrappers that verify the availability of the specified dependencies at runtime, and ensure that @@ -729,9 +716,8 @@ This will do a checkout (or a clone, in Git and Mercurial parlance) to a temporary folder and run ``setup.py bdist_egg``. The ``dependency_links`` option takes the form of a list of URL strings. For -example, the below will cause EasyInstall to search the specified page for -eggs or source distributions, if the package's dependencies aren't already -installed:: +example, this will cause a search of the specified page for eggs or source +distributions, if the package's dependencies aren't already installed:: setup( ... @@ -771,7 +757,7 @@ names of "extra" features, to strings or lists of strings describing those features' requirements. These requirements will *not* be automatically installed unless another package depends on them (directly or indirectly) by including the desired "extras" in square brackets after the associated project -name. (Or if the extras were listed in a requirement spec on the EasyInstall +name. (Or if the extras were listed in a requirement spec on the "pip install" command line.) Extras can be used by a project's `entry points`_ to specify dynamic @@ -1186,13 +1172,12 @@ preferred way of working (as opposed to using a common independent staging area or the site-packages directory). To do this, use the ``setup.py develop`` command. It works very similarly to -``setup.py install`` or the EasyInstall tool, except that it doesn't actually -install anything. Instead, it creates a special ``.egg-link`` file in the -deployment directory, that links to your project's source code. And, if your -deployment directory is Python's ``site-packages`` directory, it will also -update the ``easy-install.pth`` file to include your project's source code, -thereby making it available on ``sys.path`` for all programs using that Python -installation. +``setup.py install``, except that it doesn't actually install anything. +Instead, it creates a special ``.egg-link`` file in the deployment directory, +that links to your project's source code. And, if your deployment directory is +Python's ``site-packages`` directory, it will also update the +``easy-install.pth`` file to include your project's source code, thereby making +it available on ``sys.path`` for all programs using that Python installation. If you have enabled the ``use_2to3`` flag, then of course the ``.egg-link`` will not link directly to your source code when run under Python 3, since @@ -1312,20 +1297,14 @@ you've checked over all the warnings it issued, and you are either satisfied it doesn't work, you can always change it to ``False``, which will force ``setuptools`` to install your project as a directory rather than as a zipfile. -Of course, the end-user can still override either decision, if they are using -EasyInstall to install your package. And, if you want to override for testing -purposes, you can just run ``setup.py easy_install --zip-ok .`` or ``setup.py -easy_install --always-unzip .`` in your project directory. to install the -package as a zipfile or directory, respectively. - In the future, as we gain more experience with different packages and become more satisfied with the robustness of the ``pkg_resources`` runtime, the "zip safety" analysis may become less conservative. However, we strongly recommend that you determine for yourself whether your project functions correctly when installed as a zipfile, correct any problems if you can, and then make an explicit declaration of ``True`` or ``False`` for the ``zip_safe`` -flag, so that it will not be necessary for ``bdist_egg`` or ``EasyInstall`` to -try to guess whether your project can work as a zipfile. +flag, so that it will not be necessary for ``bdist_egg`` to try to guess +whether your project can work as a zipfile. .. _Namespace Packages: @@ -1439,9 +1418,9 @@ to generate a daily build or snapshot for. See the section below on the (Also, before you release your project, be sure to see the section above on `Specifying Your Project's Version`_ for more information about how pre- and -post-release tags affect how setuptools and EasyInstall interpret version -numbers. This is important in order to make sure that dependency processing -tools will know which versions of your project are newer than others.) +post-release tags affect how version numbers are interpreted. This is +important in order to make sure that dependency processing tools will know +which versions of your project are newer than others.) Finally, if you are creating builds frequently, and either building them in a downloadable location or are copying them to a distribution server, you should @@ -1497,58 +1476,6 @@ all practical purposes, you'll probably use only the ``--formats`` option, if you use any option at all. -Making your package available for EasyInstall ---------------------------------------------- - -There may be reasons why you don't want to upload distributions to -PyPI, and just want your existing distributions (or perhaps a Subversion -checkout) to be used instead. - -There are three ``setup()`` arguments that affect EasyInstall: - -``url`` and ``download_url`` - These become links on your project's PyPI page. EasyInstall will examine - them to see if they link to a package ("primary links"), or whether they are - HTML pages. If they're HTML pages, EasyInstall scans all HREF's on the - page for primary links - -``long_description`` - EasyInstall will check any URLs contained in this argument to see if they - are primary links. - -A URL is considered a "primary link" if it is a link to a .tar.gz, .tgz, .zip, -.egg, .egg.zip, .tar.bz2, or .exe file, or if it has an ``#egg=project`` or -``#egg=project-version`` fragment identifier attached to it. EasyInstall -attempts to determine a project name and optional version number from the text -of a primary link *without* downloading it. When it has found all the primary -links, EasyInstall will select the best match based on requested version, -platform compatibility, and other criteria. - -So, if your ``url`` or ``download_url`` point either directly to a downloadable -source distribution, or to HTML page(s) that have direct links to such, then -EasyInstall will be able to locate downloads automatically. If you want to -make Subversion checkouts available, then you should create links with either -``#egg=project`` or ``#egg=project-version`` added to the URL. You should -replace ``project`` and ``version`` with the values they would have in an egg -filename. (Be sure to actually generate an egg and then use the initial part -of the filename, rather than trying to guess what the escaped form of the -project name and version number will be.) - -Note that Subversion checkout links are of lower precedence than other kinds -of distributions, so EasyInstall will not select a Subversion checkout for -downloading unless it has a version included in the ``#egg=`` suffix, and -it's a higher version than EasyInstall has seen in any other links for your -project. - -As a result, it's a common practice to use mark checkout URLs with a version of -"dev" (i.e., ``#egg=projectname-dev``), so that users can do something like -this:: - - easy_install --editable projectname==dev - -in order to check out the in-development version of ``projectname``. - - Making "Official" (Non-Snapshot) Releases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1803,9 +1730,9 @@ Here are some of the options that the ``develop`` command accepts. Note that they affect the project's dependencies as well as the project itself, so if you have dependencies that need to be installed and you use ``--exclude-scripts`` (for example), the dependencies' scripts will not be installed either! For -this reason, you may want to use EasyInstall to install the project's -dependencies before using the ``develop`` command, if you need finer control -over the installation options for dependencies. +this reason, you may want to use pip to install the project's dependencies +before using the ``develop`` command, if you need finer control over the +installation options for dependencies. ``--uninstall, -u`` Un-deploy the current project. You may use the ``--install-dir`` or ``-d`` @@ -1815,10 +1742,10 @@ over the installation options for dependencies. staging area is Python's ``site-packages`` directory. Note that this option currently does *not* uninstall script wrappers! You - must uninstall them yourself, or overwrite them by using EasyInstall to - activate a different version of the package. You can also avoid installing - script wrappers in the first place, if you use the ``--exclude-scripts`` - (aka ``-x``) option when you run ``develop`` to deploy the project. + must uninstall them yourself, or overwrite them by using pip to install a + different version of the package. You can also avoid installing script + wrappers in the first place, if you use the ``--exclude-scripts`` (aka + ``-x``) option when you run ``develop`` to deploy the project. ``--multi-version, -m`` "Multi-version" mode. Specifying this option prevents ``develop`` from @@ -1827,8 +1754,8 @@ over the installation options for dependencies. removed upon successful deployment. In multi-version mode, no specific version of the package is available for importing, unless you use ``pkg_resources.require()`` to put it on ``sys.path``, or you are running - a wrapper script generated by ``setuptools`` or EasyInstall. (In which - case the wrapper script calls ``require()`` for you.) + a wrapper script generated by ``setuptools``. (In which case the wrapper + script calls ``require()`` for you.) Note that if you install to a directory other than ``site-packages``, this option is automatically in effect, because ``.pth`` files can only be @@ -1881,25 +1808,6 @@ files), the ``develop`` command will use them as defaults, unless you override them in a ``[develop]`` section or on the command line. -``easy_install`` - Find and install packages -============================================ - -This command runs the `EasyInstall tool -`_ for you. It is exactly -equivalent to running the ``easy_install`` command. All command line arguments -following this command are consumed and not processed further by the distutils, -so this must be the last command listed on the command line. Please see -the EasyInstall documentation for the options reference and usage examples. -Normally, there is no reason to use this command via the command line, as you -can just use ``easy_install`` directly. It's only listed here so that you know -it's a distutils command, which means that you can: - -* create command aliases that use it, -* create distutils extensions that invoke it as a subcommand, and -* configure options for it in your ``setup.cfg`` or other distutils config - files. - - .. _egg_info: ``egg_info`` - Create egg metadata and set build tags @@ -1958,9 +1866,9 @@ added in the following order: (Note: Because these options modify the version number used for source and binary distributions of your project, you should first make sure that you know how the resulting version numbers will be interpreted by automated tools -like EasyInstall. See the section above on `Specifying Your Project's -Version`_ for an explanation of pre- and post-release tags, as well as tips on -how to choose and verify a versioning scheme for your your project.) +like pip. See the section above on `Specifying Your Project's Version`_ for an +explanation of pre- and post-release tags, as well as tips on how to choose and +verify a versioning scheme for your your project.) For advanced uses, there is one other option that can be set, to change the location of the project's ``.egg-info`` directory. Commands that need to find -- cgit v1.2.3 From 1410d87f8abb5bb28bf97f53219ee0db7b6340a3 Mon Sep 17 00:00:00 2001 From: isidentical Date: Fri, 4 Oct 2019 19:18:54 +0300 Subject: Upgrade setuptools.depends to importlib from depracated imp --- setuptools/depends.py | 89 ++++++++++++++++++++++++++++++------ setuptools/tests/test_integration.py | 1 + 2 files changed, 76 insertions(+), 14 deletions(-) diff --git a/setuptools/depends.py b/setuptools/depends.py index 45e7052d..97f0ed9d 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,11 +1,23 @@ import sys -import imp import marshal from distutils.version import StrictVersion -from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN +from setuptools.extern import six from .py33compat import Bytecode +if six.PY2: + import imp + from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN +else: + import os.path + from importlib.util import find_spec, spec_from_loader + from importlib.machinery import SOURCE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES, BuiltinImporter, FrozenImporter + PY_SOURCE = 1 + PY_COMPILED = 2 + C_EXTENSION = 3 + C_BUILTIN = 6 + PY_FROZEN = 7 + __all__ = [ 'Require', 'find_module', 'get_module_constant', 'extract_constant' @@ -81,21 +93,59 @@ class Require: def find_module(module, paths=None): """Just like 'imp.find_module()', but with package support""" + if six.PY3: + spec = find_spec(module, paths) + if spec is None: + raise ImportError("Can't find %s" % module) + if not spec.has_location and hasattr(spec, 'submodule_search_locations'): + spec = spec_from_loader('__init__.py', spec.loader) + + kind = -1 + file = None + static = isinstance(spec.loader, type) + if spec.origin == 'frozen' or static and issubclass(spec.loader, FrozenImporter): + kind = PY_FROZEN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.origin == 'built-in' or static and issubclass(spec.loader, BuiltinImporter): + kind = C_BUILTIN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.has_location: + frozen = False + path = spec.origin + suffix = os.path.splitext(path)[1] + mode = 'r' if suffix in SOURCE_SUFFIXES else 'rb' + + if suffix in SOURCE_SUFFIXES: + kind = PY_SOURCE + elif suffix in BYTECODE_SUFFIXES: + kind = PY_COMPILED + elif suffix in EXTENSION_SUFFIXES: + kind = C_EXTENSION + + if kind in {PY_SOURCE, PY_COMPILED}: + file = open(path, mode) + else: + path = None + suffix = mode= '' - parts = module.split('.') + return file, path, (suffix, mode, kind) - while parts: - part = parts.pop(0) - f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) + else: + parts = module.split('.') + while parts: + part = parts.pop(0) + f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) - if kind == PKG_DIRECTORY: - parts = parts or ['__init__'] - paths = [path] + if kind == PKG_DIRECTORY: + parts = parts or ['__init__'] + paths = [path] - elif parts: - raise ImportError("Can't find %r in %s" % (parts, module)) + elif parts: + raise ImportError("Can't find %r in %s" % (parts, module)) - return info + return info def get_module_constant(module, symbol, default=-1, paths=None): @@ -111,18 +161,29 @@ def get_module_constant(module, symbol, default=-1, paths=None): # Module doesn't exist return None + if six.PY3: + spec = find_spec(module, paths) + if hasattr(spec, 'submodule_search_locations'): + spec = spec_from_loader('__init__.py', spec.loader) + try: if kind == PY_COMPILED: f.read(8) # skip magic & date code = marshal.load(f) elif kind == PY_FROZEN: - code = imp.get_frozen_object(module) + if six.PY2: + code = imp.get_frozen_object(module) + else: + code = spec.loader.get_code(module) elif kind == PY_SOURCE: code = compile(f.read(), path, 'exec') else: # Not something we can parse; we'll have to import it. :( if module not in sys.modules: - imp.load_module(module, f, path, (suffix, mode, kind)) + if six.PY2: + imp.load_module(module, f, path, (suffix, mode, kind)) + else: + sys.modules[module] = module_from_spec(spec) return getattr(sys.modules[module], symbol, None) finally: diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 1e132188..1c0b2b18 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -143,6 +143,7 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep): 'tests_require', 'python_requires', 'install_requires', + 'long_description_content_type', ] assert not match or match.group(1).strip('"\'') in allowed_unknowns -- cgit v1.2.3 From 4c22a6ca57753d3b5604a90b61a0c6c5efe53a1d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 18 Oct 2019 20:37:42 -0400 Subject: Add new hook 'setuptools.finalize_distribution_options' for plugins like 'setuptools_scm' to alter distribution options. --- setuptools/dist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setuptools/dist.py b/setuptools/dist.py index 2e5ad4bd..987d684e 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -728,6 +728,10 @@ class Distribution(_Distribution): if self.features: self._set_global_opts_from_features() + hook_key = 'setuptools.finalize_distribution_options' + for ep in pkg_resources.iter_entry_points(hook_key): + ep.load()(self) + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): value = getattr(self, ep.name, None) if value is not None: -- cgit v1.2.3 From d89682fcba90595d5d6aaf071d6efcc815bceba8 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 21 Oct 2019 16:49:13 -0700 Subject: Change coding cookie to use utf-8 (lowercase) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While perfectly valid, the encoding 'UTF-8' (uppercase) is not recognized by the Emacs MULE system. As such, it displays the following warning when opening a file with it used as an encoding cookie: Warning (mule): Invalid coding system ‘UTF-8’ is specified for the current buffer/file by the :coding tag. It is highly recommended to fix it before writing to a file. Some discussion of this can be found at: https://stackoverflow.com/questions/14031724/how-to-make-emacs-accept-utf-8-uppercase-encoding While the post does offer a workaround for Emacs users, rather than ask all to implement it, use the more typical utf-8 (lowercase). --- setuptools/tests/test_config.py | 2 +- setuptools/tests/test_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 1b94a586..69d8d00d 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -1,4 +1,4 @@ -# -*- coding: UTF-8 -*- +# -*- coding: utf-8 -*- from __future__ import unicode_literals import contextlib diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index faaa6ba9..3415913b 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -1,4 +1,4 @@ -# -*- coding: UTF-8 -*- +# -*- coding: utf-8 -*- from __future__ import unicode_literals -- cgit v1.2.3 From cd84510713ada48bf33d4efa749c2952e3fc1a49 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 19 Oct 2019 08:39:30 -0700 Subject: Deprecate the test command Provide a warning to users. Suggest using tox as an alternative generic entry point. Refs #1684 --- changelog.d/1878.change.rst | 1 + docs/setuptools.txt | 13 +++++++++++ setuptools/command/test.py | 10 ++++++++- setuptools/tests/test_test.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1878.change.rst diff --git a/changelog.d/1878.change.rst b/changelog.d/1878.change.rst new file mode 100644 index 00000000..0774b5d3 --- /dev/null +++ b/changelog.d/1878.change.rst @@ -0,0 +1 @@ +Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 26a3044e..f69b75c2 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -346,6 +346,8 @@ unless you need the associated ``setuptools`` feature. specified test suite, e.g. via ``setup.py test``. See the section on the `test`_ command below for more details. + New in 41.5.0: Deprecated the test command. + ``tests_require`` If your project's tests need one or more additional packages besides those needed to install it, you can use this option to specify them. It should @@ -357,6 +359,8 @@ unless you need the associated ``setuptools`` feature. are run, but only downloaded to the project's setup directory if they're not already installed locally. + New in 41.5.0: Deprecated the test command. + .. _test_loader: ``test_loader`` @@ -380,6 +384,8 @@ unless you need the associated ``setuptools`` feature. as long as you use the ``tests_require`` option to ensure that the package containing the loader class is available when the ``test`` command is run. + New in 41.5.0: Deprecated the test command. + ``eager_resources`` A list of strings naming resources that should be extracted together, if any of them is needed, or if any C extensions included in the project are @@ -2142,6 +2148,11 @@ distutils configuration file the option will be added to (or removed from). ``test`` - Build package and run a unittest suite ================================================= +.. warning:: + ``test`` is deprecated and will be removed in a future version. Users + looking for a generic test entry point independent of test runner are + encouraged to use `tox `_. + When doing test-driven development, or running automated builds that need testing before they are deployed for downloading or use, it's often useful to be able to run a project's unit tests without actually deploying the project @@ -2187,6 +2198,8 @@ available: If you did not set a ``test_suite`` in your ``setup()`` call, and do not provide a ``--test-suite`` option, an error will occur. +New in 41.5.0: Deprecated the test command. + .. _upload: diff --git a/setuptools/command/test.py b/setuptools/command/test.py index 973e4eb2..c148b38d 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -74,7 +74,7 @@ class NonDataProperty: class test(Command): """Command to run unit tests after in-place build""" - description = "run unit tests after in-place build" + description = "run unit tests after in-place build (deprecated)" user_options = [ ('test-module=', 'm', "Run 'test_suite' in specified module"), @@ -214,6 +214,14 @@ class test(Command): return itertools.chain(ir_d, tr_d, er_d) def run(self): + self.announce( + "WARNING: Testing via this command is deprecated and will be " + "removed in a future version. Users looking for a generic test " + "entry point independent of test runner are encouraged to use " + "tox.", + log.WARN, + ) + installed_dists = self.install_dists(self.distribution) cmd = ' '.join(self._argv) diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index faaa6ba9..382bd640 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals +import mock from distutils import log import os @@ -124,3 +125,52 @@ def test_tests_are_run_once(capfd): cmd.run() out, err = capfd.readouterr() assert out == 'Foo\n' + + +@pytest.mark.usefixtures('sample_test') +def test_warns_deprecation(capfd): + params = dict( + name='foo', + packages=['name', 'name.space', 'name.space.tests'], + namespace_packages=['name'], + test_suite='name.space.tests.test_suite', + use_2to3=True + ) + dist = Distribution(params) + dist.script_name = 'setup.py' + cmd = test(dist) + cmd.ensure_finalized() + cmd.announce = mock.Mock() + cmd.run() + capfd.readouterr() + msg = ( + "WARNING: Testing via this command is deprecated and will be " + "removed in a future version. Users looking for a generic test " + "entry point independent of test runner are encouraged to use " + "tox." + ) + cmd.announce.assert_any_call(msg, log.WARN) + + +@pytest.mark.usefixtures('sample_test') +def test_deprecation_stderr(capfd): + params = dict( + name='foo', + packages=['name', 'name.space', 'name.space.tests'], + namespace_packages=['name'], + test_suite='name.space.tests.test_suite', + use_2to3=True + ) + dist = Distribution(params) + dist.script_name = 'setup.py' + cmd = test(dist) + cmd.ensure_finalized() + cmd.run() + out, err = capfd.readouterr() + msg = ( + "WARNING: Testing via this command is deprecated and will be " + "removed in a future version. Users looking for a generic test " + "entry point independent of test runner are encouraged to use " + "tox.\n" + ) + assert msg in err -- cgit v1.2.3 From 4069e0b536802a47c8c15ce839fd98a5f4c84620 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 21 Oct 2019 17:01:18 -0700 Subject: Remove outdated comment and suppressed exception from test_test.py The test command has not called sys.exit since commit 2c4fd43277fc477d85b50e15c37b176136676270. --- setuptools/tests/test_test.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index 280c837b..6242a018 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -86,9 +86,7 @@ def test_test(capfd): dist.script_name = 'setup.py' cmd = test(dist) cmd.ensure_finalized() - # The test runner calls sys.exit - with contexts.suppress_exceptions(SystemExit): - cmd.run() + cmd.run() out, err = capfd.readouterr() assert out == 'Foo\n' @@ -120,9 +118,7 @@ def test_tests_are_run_once(capfd): dist.script_name = 'setup.py' cmd = test(dist) cmd.ensure_finalized() - # The test runner calls sys.exit - with contexts.suppress_exceptions(SystemExit): - cmd.run() + cmd.run() out, err = capfd.readouterr() assert out == 'Foo\n' -- cgit v1.2.3 From e4ef537525c2b1d120497379da9484c33ea3d773 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 21 Oct 2019 17:51:26 -0700 Subject: Add a trove classifier to document support for Python 3.8 --- changelog.d/1884.doc.rst | 1 + setup.cfg | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/1884.doc.rst diff --git a/changelog.d/1884.doc.rst b/changelog.d/1884.doc.rst new file mode 100644 index 00000000..45615d5d --- /dev/null +++ b/changelog.d/1884.doc.rst @@ -0,0 +1 @@ +Added a trove classifier to document support for Python 3.8. diff --git a/setup.cfg b/setup.cfg index a55106a5..373ae8b5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,7 @@ classifiers = Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 Topic :: Software Development :: Libraries :: Python Modules Topic :: System :: Archiving :: Packaging Topic :: System :: Systems Administration -- cgit v1.2.3 From 89f15f448679e2ebedb40ed348423596dc658d31 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 21 Oct 2019 17:51:26 -0700 Subject: Add Python 3.8 final to Travis test matrix --- .travis.yml | 3 ++- changelog.d/1886.misc.rst | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1886.misc.rst diff --git a/.travis.yml b/.travis.yml index 8b7cece8..7088d166 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,9 @@ jobs: - python: 3.5 - &default_py python: 3.6 + - python: 3.7 - &latest_py3 - python: 3.7 + python: 3.8 - <<: *latest_py3 env: LANG=C - python: 3.8-dev diff --git a/changelog.d/1886.misc.rst b/changelog.d/1886.misc.rst new file mode 100644 index 00000000..5e3f2873 --- /dev/null +++ b/changelog.d/1886.misc.rst @@ -0,0 +1 @@ +Added Python 3.8 release to the Travis test matrix. -- cgit v1.2.3 From 19eb6bf8bd28f1b9b66288c797d67eb8da71508d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 18:26:59 -0400 Subject: Fix typo --- docs/easy_install.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/easy_install.txt b/docs/easy_install.txt index e247d8fd..544b9efd 100644 --- a/docs/easy_install.txt +++ b/docs/easy_install.txt @@ -317,7 +317,7 @@ Note that instead of changing your ``PATH`` to include the Python scripts directory, you can also retarget the installation location for scripts so they go on a directory that's already on the ``PATH``. For more information see `Command-Line Options`_ and `Configuration Files`_. During installation, -pass command line options (such as ``--script-dir``) to to control where +pass command line options (such as ``--script-dir``) to control where scripts will be installed. -- cgit v1.2.3 From 6e8b1da1fb146186c52d8bdec5af969f26532ece Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 18:42:56 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.4.0=20=E2=86=92=2041.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 14 ++++++++++++++ changelog.d/1811.change.rst | 1 - changelog.d/1814.change.rst | 1 - changelog.d/1824.change.rst | 1 - changelog.d/1860.doc.rst | 1 - changelog.d/1862.doc.rst | 1 - changelog.d/1868.doc.rst | 1 - changelog.d/1878.change.rst | 1 - changelog.d/1884.doc.rst | 1 - changelog.d/1886.misc.rst | 1 - setup.cfg | 2 +- 12 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 changelog.d/1811.change.rst delete mode 100644 changelog.d/1814.change.rst delete mode 100644 changelog.d/1824.change.rst delete mode 100644 changelog.d/1860.doc.rst delete mode 100644 changelog.d/1862.doc.rst delete mode 100644 changelog.d/1868.doc.rst delete mode 100644 changelog.d/1878.change.rst delete mode 100644 changelog.d/1884.doc.rst delete mode 100644 changelog.d/1886.misc.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d6768cb8..aeefc8f2 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.4.0 +current_version = 41.5.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index ecde25a5..7e9fc0f6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,17 @@ +v41.5.0 +------- + +* #1811: Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019. +* #1814: Fix ``pkg_resources.Requirement`` hash/equality implementation: take PEP 508 direct URL into account. +* #1824: Fix tests when running under ``python3.10``. +* #1878: Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``. +* #1860: Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0. +* #1862: Drop ez_setup documentation: deprecated for some time (last updated in 2016), and still relying on easy_install (deprecated too). +* #1868: Drop most documentation references to (deprecated) EasyInstall. +* #1884: Added a trove classifier to document support for Python 3.8. +* #1886: Added Python 3.8 release to the Travis test matrix. + + v41.4.0 ------- diff --git a/changelog.d/1811.change.rst b/changelog.d/1811.change.rst deleted file mode 100644 index dc52c6db..00000000 --- a/changelog.d/1811.change.rst +++ /dev/null @@ -1 +0,0 @@ -Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019. \ No newline at end of file diff --git a/changelog.d/1814.change.rst b/changelog.d/1814.change.rst deleted file mode 100644 index c936699d..00000000 --- a/changelog.d/1814.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ``pkg_resources.Requirement`` hash/equality implementation: take PEP 508 direct URL into account. diff --git a/changelog.d/1824.change.rst b/changelog.d/1824.change.rst deleted file mode 100644 index 5f609036..00000000 --- a/changelog.d/1824.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix tests when running under ``python3.10``. diff --git a/changelog.d/1860.doc.rst b/changelog.d/1860.doc.rst deleted file mode 100644 index f3554643..00000000 --- a/changelog.d/1860.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0. diff --git a/changelog.d/1862.doc.rst b/changelog.d/1862.doc.rst deleted file mode 100644 index b71583ba..00000000 --- a/changelog.d/1862.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Drop ez_setup documentation: deprecated for some time (last updated in 2016), and still relying on easy_install (deprecated too). diff --git a/changelog.d/1868.doc.rst b/changelog.d/1868.doc.rst deleted file mode 100644 index 82283d7a..00000000 --- a/changelog.d/1868.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Drop most documentation references to (deprecated) EasyInstall. diff --git a/changelog.d/1878.change.rst b/changelog.d/1878.change.rst deleted file mode 100644 index 0774b5d3..00000000 --- a/changelog.d/1878.change.rst +++ /dev/null @@ -1 +0,0 @@ -Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``. diff --git a/changelog.d/1884.doc.rst b/changelog.d/1884.doc.rst deleted file mode 100644 index 45615d5d..00000000 --- a/changelog.d/1884.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added a trove classifier to document support for Python 3.8. diff --git a/changelog.d/1886.misc.rst b/changelog.d/1886.misc.rst deleted file mode 100644 index 5e3f2873..00000000 --- a/changelog.d/1886.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Added Python 3.8 release to the Travis test matrix. diff --git a/setup.cfg b/setup.cfg index 373ae8b5..0e030cde 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.4.0 +version = 41.5.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From f430e585d84a5c63bb3b52e17af2f1b40fec8b71 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 19:16:36 -0400 Subject: Remove apparently unrelated change to test --- setuptools/tests/test_integration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 1c0b2b18..1e132188 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -143,7 +143,6 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep): 'tests_require', 'python_requires', 'install_requires', - 'long_description_content_type', ] assert not match or match.group(1).strip('"\'') in allowed_unknowns -- cgit v1.2.3 From 85a9ca5e75abf00e0dde55dde4e2b0a11f93c04a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 19:50:53 -0400 Subject: Extract 'imp' re-implementation to setuptools._imp and wrap it in py27compat for compatibility. --- conftest.py | 1 + setuptools/_imp.py | 72 ++++++++++++++++++++++++++++++++++++ setuptools/depends.py | 95 ++++-------------------------------------------- setuptools/py27compat.py | 32 ++++++++++++++++ 4 files changed, 113 insertions(+), 87 deletions(-) create mode 100644 setuptools/_imp.py diff --git a/conftest.py b/conftest.py index 0d7b274c..1746bfb5 100644 --- a/conftest.py +++ b/conftest.py @@ -19,6 +19,7 @@ collect_ignore = [ if sys.version_info < (3,): collect_ignore.append('setuptools/lib2to3_ex.py') + collect_ignore.append('setuptools/_imp.py') if sys.version_info < (3, 6): diff --git a/setuptools/_imp.py b/setuptools/_imp.py new file mode 100644 index 00000000..6bc90243 --- /dev/null +++ b/setuptools/_imp.py @@ -0,0 +1,72 @@ +""" +Re-implementation of find_module and get_frozen_object +from the deprecated imp module. +""" + +import os +import importlib.util +import importlib.machinery + + +PY_SOURCE = 1 +PY_COMPILED = 2 +C_EXTENSION = 3 +C_BUILTIN = 6 +PY_FROZEN = 7 + + +def find_module(module, paths=None): + """ + """ + spec = importlib.util.find_spec(module, paths) + if spec is None: + raise ImportError("Can't find %s" % module) + if not spec.has_location and hasattr(spec, 'submodule_search_locations'): + spec = importlib.util.spec_from_loader('__init__.py', spec.loader) + + kind = -1 + file = None + static = isinstance(spec.loader, type) + if spec.origin == 'frozen' or static and issubclass( + spec.loader, importlib.machinery.FrozenImporter): + kind = PY_FROZEN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.origin == 'built-in' or static and issubclass( + spec.loader, importlib.machinery.BuiltinImporter): + kind = C_BUILTIN + path = None # imp compabilty + suffix = mode = '' # imp compability + elif spec.has_location: + path = spec.origin + suffix = os.path.splitext(path)[1] + mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb' + + if suffix in importlib.machinery.SOURCE_SUFFIXES: + kind = PY_SOURCE + elif suffix in importlib.machinery.BYTECODE_SUFFIXES: + kind = PY_COMPILED + elif suffix in importlib.machinery.EXTENSION_SUFFIXES: + kind = C_EXTENSION + + if kind in {PY_SOURCE, PY_COMPILED}: + file = open(path, mode) + else: + path = None + suffix = mode = '' + + return file, path, (suffix, mode, kind) + + +def get_frozen_object(module, paths): + spec = importlib.util.find_spec(module, paths) + if hasattr(spec, 'submodule_search_locations'): + spec = importlib.util.spec_from_loader('__init__.py', spec.loader) + return spec.loader.get_code(module) + + +def get_module(module, paths, info): + spec = importlib.util.find_spec(module, paths) + if hasattr(spec, 'submodule_search_locations'): + spec = importlib.util.spec_from_loader('__init__.py', spec.loader) + return importlib.util.module_from_spec(spec) diff --git a/setuptools/depends.py b/setuptools/depends.py index 97f0ed9d..eed4913a 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,22 +1,11 @@ import sys import marshal from distutils.version import StrictVersion -from setuptools.extern import six from .py33compat import Bytecode -if six.PY2: - import imp - from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN -else: - import os.path - from importlib.util import find_spec, spec_from_loader - from importlib.machinery import SOURCE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES, BuiltinImporter, FrozenImporter - PY_SOURCE = 1 - PY_COMPILED = 2 - C_EXTENSION = 3 - C_BUILTIN = 6 - PY_FROZEN = 7 +from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE +from . import py27compat __all__ = [ @@ -27,7 +16,8 @@ __all__ = [ class Require: """A prerequisite to building or installing a distribution""" - def __init__(self, name, requested_version, module, homepage='', + def __init__( + self, name, requested_version, module, homepage='', attribute=None, format=None): if format is None and requested_version is not None: @@ -91,63 +81,6 @@ class Require: return self.version_ok(version) -def find_module(module, paths=None): - """Just like 'imp.find_module()', but with package support""" - if six.PY3: - spec = find_spec(module, paths) - if spec is None: - raise ImportError("Can't find %s" % module) - if not spec.has_location and hasattr(spec, 'submodule_search_locations'): - spec = spec_from_loader('__init__.py', spec.loader) - - kind = -1 - file = None - static = isinstance(spec.loader, type) - if spec.origin == 'frozen' or static and issubclass(spec.loader, FrozenImporter): - kind = PY_FROZEN - path = None # imp compabilty - suffix = mode = '' # imp compability - elif spec.origin == 'built-in' or static and issubclass(spec.loader, BuiltinImporter): - kind = C_BUILTIN - path = None # imp compabilty - suffix = mode = '' # imp compability - elif spec.has_location: - frozen = False - path = spec.origin - suffix = os.path.splitext(path)[1] - mode = 'r' if suffix in SOURCE_SUFFIXES else 'rb' - - if suffix in SOURCE_SUFFIXES: - kind = PY_SOURCE - elif suffix in BYTECODE_SUFFIXES: - kind = PY_COMPILED - elif suffix in EXTENSION_SUFFIXES: - kind = C_EXTENSION - - if kind in {PY_SOURCE, PY_COMPILED}: - file = open(path, mode) - else: - path = None - suffix = mode= '' - - return file, path, (suffix, mode, kind) - - else: - parts = module.split('.') - while parts: - part = parts.pop(0) - f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) - - if kind == PKG_DIRECTORY: - parts = parts or ['__init__'] - paths = [path] - - elif parts: - raise ImportError("Can't find %r in %s" % (parts, module)) - - return info - - def get_module_constant(module, symbol, default=-1, paths=None): """Find 'module' by searching 'paths', and extract 'symbol' @@ -156,35 +89,23 @@ def get_module_constant(module, symbol, default=-1, paths=None): constant. Otherwise, return 'default'.""" try: - f, path, (suffix, mode, kind) = find_module(module, paths) + f, path, (suffix, mode, kind) = info = find_module(module, paths) except ImportError: # Module doesn't exist return None - if six.PY3: - spec = find_spec(module, paths) - if hasattr(spec, 'submodule_search_locations'): - spec = spec_from_loader('__init__.py', spec.loader) - try: if kind == PY_COMPILED: f.read(8) # skip magic & date code = marshal.load(f) elif kind == PY_FROZEN: - if six.PY2: - code = imp.get_frozen_object(module) - else: - code = spec.loader.get_code(module) + code = py27compat.get_frozen_object(module, paths) elif kind == PY_SOURCE: code = compile(f.read(), path, 'exec') else: # Not something we can parse; we'll have to import it. :( - if module not in sys.modules: - if six.PY2: - imp.load_module(module, f, path, (suffix, mode, kind)) - else: - sys.modules[module] = module_from_spec(spec) - return getattr(sys.modules[module], symbol, None) + imported = py27compat.get_module(module, paths, info) + return getattr(imported, symbol, None) finally: if f: diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py index 2985011b..cf5fb33e 100644 --- a/setuptools/py27compat.py +++ b/setuptools/py27compat.py @@ -2,6 +2,7 @@ Compatibility Support for Python 2.7 and earlier """ +import sys import platform from setuptools.extern import six @@ -26,3 +27,34 @@ linux_py2_ascii = ( rmtree_safe = str if linux_py2_ascii else lambda x: x """Workaround for http://bugs.python.org/issue24672""" + + +try: + from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE + from ._imp import get_frozen_object, get_module +except ImportError: + import imp + from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE # noqa + + def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + parts = module.split('.') + while parts: + part = parts.pop(0) + f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) + + if kind == imp.PKG_DIRECTORY: + parts = parts or ['__init__'] + paths = [path] + + elif parts: + raise ImportError("Can't find %r in %s" % (parts, module)) + + return info + + def get_frozen_object(module, paths): + return imp.get_frozen_object(module) + + def get_module(module, paths, info): + imp.load_module(*info) + return sys.modules[module] -- cgit v1.2.3 From 773f1ec3c2622a78ee0280eb1d2b03c60871c52b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 19:54:42 -0400 Subject: Rely on contextlib.closing for brevity. --- setuptools/depends.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/setuptools/depends.py b/setuptools/depends.py index eed4913a..a37675cb 100644 --- a/setuptools/depends.py +++ b/setuptools/depends.py @@ -1,5 +1,6 @@ import sys import marshal +import contextlib from distutils.version import StrictVersion from .py33compat import Bytecode @@ -81,6 +82,17 @@ class Require: return self.version_ok(version) +def maybe_close(f): + @contextlib.contextmanager + def empty(): + yield + return + if not f: + return empty() + + return contextlib.closing(f) + + def get_module_constant(module, symbol, default=-1, paths=None): """Find 'module' by searching 'paths', and extract 'symbol' @@ -94,7 +106,7 @@ def get_module_constant(module, symbol, default=-1, paths=None): # Module doesn't exist return None - try: + with maybe_close(f): if kind == PY_COMPILED: f.read(8) # skip magic & date code = marshal.load(f) @@ -107,10 +119,6 @@ def get_module_constant(module, symbol, default=-1, paths=None): imported = py27compat.get_module(module, paths, info) return getattr(imported, symbol, None) - finally: - if f: - f.close() - return extract_constant(code, symbol, default) -- cgit v1.2.3 From 37d617cd983446dfe8073038d03dd31fc7f66796 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 20:40:07 -0400 Subject: Extract _resolve --- setuptools/_imp.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index 6bc90243..c400d455 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -60,13 +60,17 @@ def find_module(module, paths=None): def get_frozen_object(module, paths): spec = importlib.util.find_spec(module, paths) - if hasattr(spec, 'submodule_search_locations'): - spec = importlib.util.spec_from_loader('__init__.py', spec.loader) - return spec.loader.get_code(module) + return spec.loader.get_code(_resolve(module)) + + +def _resolve(spec): + return ( + importlib.util.spec_from_loader('__init__.py', spec.loader) + if hasattr(spec, 'submodule_search_locations') + else spec + ) def get_module(module, paths, info): spec = importlib.util.find_spec(module, paths) - if hasattr(spec, 'submodule_search_locations'): - spec = importlib.util.spec_from_loader('__init__.py', spec.loader) - return importlib.util.module_from_spec(spec) + return importlib.util.module_from_spec(_resolve(spec)) -- cgit v1.2.3 From 4948b14a66f00eba749e52b77281f711413e071b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 20:40:58 -0400 Subject: Avoid _resolve in get_module (causes failures). --- setuptools/_imp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index c400d455..cee91551 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -73,4 +73,4 @@ def _resolve(spec): def get_module(module, paths, info): spec = importlib.util.find_spec(module, paths) - return importlib.util.module_from_spec(_resolve(spec)) + return importlib.util.module_from_spec(spec) -- cgit v1.2.3 From 16051d6b2d3641dc8951e90f7f04bcd04b8954d3 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 28 Oct 2019 10:12:53 +0300 Subject: imp load_module fix --- setuptools/py27compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py index cf5fb33e..1d57360f 100644 --- a/setuptools/py27compat.py +++ b/setuptools/py27compat.py @@ -56,5 +56,5 @@ except ImportError: return imp.get_frozen_object(module) def get_module(module, paths, info): - imp.load_module(*info) + imp.load_module(module, *info) return sys.modules[module] -- cgit v1.2.3 From 65fe7abeaba9e82b8f3755054759fed21c0c489b Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 28 Oct 2019 10:28:55 +0300 Subject: py34 compat --- setuptools/_imp.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index cee91551..09073d44 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -4,6 +4,7 @@ from the deprecated imp module. """ import os +import sys import importlib.util import importlib.machinery @@ -70,7 +71,12 @@ def _resolve(spec): else spec ) +def _module_from_spec(spec): + if sys.version_info >= (3, 5): + return importlib.util.module_from_spec(spec) + else: + return spec.loader.load_module(spec.name) def get_module(module, paths, info): spec = importlib.util.find_spec(module, paths) - return importlib.util.module_from_spec(spec) + return _module_from_spec(spec) -- cgit v1.2.3 From ce01c0199f93612848e664bfd920083168eaa293 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 28 Oct 2019 10:59:21 +0300 Subject: add docstring to find_module --- setuptools/_imp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index 09073d44..dc4b1b2c 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -17,8 +17,7 @@ PY_FROZEN = 7 def find_module(module, paths=None): - """ - """ + """Just like 'imp.find_module()', but with package support""" spec = importlib.util.find_spec(module, paths) if spec is None: raise ImportError("Can't find %s" % module) -- cgit v1.2.3 From 2f4952927e41643100e6e6f58124f34331c14add Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Mon, 28 Oct 2019 11:07:27 +0300 Subject: remove _resolve --- setuptools/_imp.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index dc4b1b2c..49ddc852 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -63,13 +63,6 @@ def get_frozen_object(module, paths): return spec.loader.get_code(_resolve(module)) -def _resolve(spec): - return ( - importlib.util.spec_from_loader('__init__.py', spec.loader) - if hasattr(spec, 'submodule_search_locations') - else spec - ) - def _module_from_spec(spec): if sys.version_info >= (3, 5): return importlib.util.module_from_spec(spec) -- cgit v1.2.3 From 7489ea4047661a7dbd46ff155abe45c548284676 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 28 Oct 2019 18:47:31 +0100 Subject: msvc: fix Python 2 support --- changelog.d/1891.change.rst | 1 + setuptools/msvc.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/1891.change.rst diff --git a/changelog.d/1891.change.rst b/changelog.d/1891.change.rst new file mode 100644 index 00000000..81ccff10 --- /dev/null +++ b/changelog.d/1891.change.rst @@ -0,0 +1 @@ +Fix code for detecting Visual Studio's version on Windows under Python 2. diff --git a/setuptools/msvc.py b/setuptools/msvc.py index ffa7053b..2ffe1c81 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -20,6 +20,7 @@ This may also support compilers shipped with compatible Visual Studio versions. """ import json +from io import open from os import listdir, pathsep from os.path import join, isfile, isdir, dirname import sys -- cgit v1.2.3 From 7748921de342160ca2dc9c9539562bb9c924e14c Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 28 Oct 2019 19:12:39 +0100 Subject: =?UTF-8?q?Bump=20version:=2041.5.0=20=E2=86=92=2041.5.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 6 ++++++ changelog.d/1891.change.rst | 1 - setup.cfg | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/1891.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index aeefc8f2..0dc75e9b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.5.0 +current_version = 41.5.1 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 7e9fc0f6..bac35638 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v41.5.1 +------- + +* #1891: Fix code for detecting Visual Studio's version on Windows under Python 2. + + v41.5.0 ------- diff --git a/changelog.d/1891.change.rst b/changelog.d/1891.change.rst deleted file mode 100644 index 81ccff10..00000000 --- a/changelog.d/1891.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix code for detecting Visual Studio's version on Windows under Python 2. diff --git a/setup.cfg b/setup.cfg index 0e030cde..8038b463 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.5.0 +version = 41.5.1 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 823ab9d2ec4ab89f90c0a781d872c9071b4afc13 Mon Sep 17 00:00:00 2001 From: Mick Koch Date: Mon, 20 May 2019 18:25:19 -0400 Subject: Add support for `license_files` option in metadata --- changelog.d/1767.change.rst | 2 + docs/setuptools.txt | 1 + setuptools/command/sdist.py | 26 +++-- setuptools/config.py | 1 + setuptools/dist.py | 1 + setuptools/tests/test_egg_info.py | 198 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 changelog.d/1767.change.rst diff --git a/changelog.d/1767.change.rst b/changelog.d/1767.change.rst new file mode 100644 index 00000000..5d42aedc --- /dev/null +++ b/changelog.d/1767.change.rst @@ -0,0 +1,2 @@ +Add support for the ``license_files`` option in ``setup.cfg`` to automatically +include multiple license files in a source distribution. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 344ea5bc..22025f61 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2276,6 +2276,7 @@ maintainer_email maintainer-email str classifiers classifier file:, list-comma license str license_file str +license_files list-comma description summary file:, str long_description long-description file:, str long_description_content_type str 38.6.0 diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index dc253981..24316640 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -200,10 +200,12 @@ class sdist(sdist_add_defaults, orig.sdist): manifest.close() def check_license(self): - """Checks if license_file' is configured and adds it to - 'self.filelist' if the value contains a valid path. + """Checks if license_file' or 'license_files' is configured and adds any + valid paths to 'self.filelist'. """ + files = set() + opts = self.distribution.get_option_dict('metadata') # ignore the source of the value @@ -211,11 +213,19 @@ class sdist(sdist_add_defaults, orig.sdist): if license_file is None: log.debug("'license_file' option was not specified") - return + else: + files.add(license_file) - if not os.path.exists(license_file): - log.warn("warning: Failed to find the configured license file '%s'", - license_file) - return + try: + files.update(self.distribution.metadata.license_files) + except TypeError: + log.warn("warning: 'license_files' option is malformed") + + for f in files: + if not os.path.exists(f): + log.warn( + "warning: Failed to find the configured license file '%s'", + f) + continue - self.filelist.append(license_file) + self.filelist.append(f) diff --git a/setuptools/config.py b/setuptools/config.py index 2d50e25e..9b9a0c45 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -483,6 +483,7 @@ class ConfigMetadataHandler(ConfigHandler): 'obsoletes': parse_list, 'classifiers': self._get_parser_compound(parse_file, parse_list), 'license': exclude_files_parser('license'), + 'license_files': parse_list, 'description': parse_file, 'long_description': parse_file, 'version': self._parse_version, diff --git a/setuptools/dist.py b/setuptools/dist.py index 2e5ad4bd..fb379a20 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -409,6 +409,7 @@ class Distribution(_Distribution): 'long_description_content_type': None, 'project_urls': dict, 'provides_extras': ordered_set.OrderedSet, + 'license_files': list, } _patched_dist = None diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 316eb2ed..61da1bda 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -567,6 +567,204 @@ class TestEggInfo: assert 'LICENSE' not in sources_text assert 'INVALID_LICENSE' not in sources_text # for invalid license test + @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [ + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = + LICENSE-ABC + LICENSE-XYZ + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = LICENSE-ABC, LICENSE-XYZ + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = + LICENSE-ABC + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = LICENSE-XYZ + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = + LICENSE-ABC + INVALID_LICENSE + """), + 'LICENSE-ABC': DALS("Test license") + }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license + ({ + 'setup.cfg': DALS(""" + """), + 'LICENSE': DALS("Test license") + }, [], ['LICENSE']), # no license_files attribute + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = LICENSE + """), + 'MANIFEST.in': DALS("exclude LICENSE"), + 'LICENSE': DALS("Test license") + }, [], ['LICENSE']), # license file is manually excluded + ({ + 'setup.cfg': DALS(""" + [metadata] + license_files = + LICENSE-ABC + LICENSE-XYZ + """), + 'MANIFEST.in': DALS("exclude LICENSE-XYZ"), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded + ]) + def test_setup_cfg_license_files( + self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): + self._create_project() + build_files(files) + + environment.run_setup_py( + cmd=['egg_info'], + pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]) + ) + egg_info_dir = os.path.join('.', 'foo.egg-info') + + with open(os.path.join(egg_info_dir, 'SOURCES.txt')) as sources_file: + sources_lines = list(line.strip() for line in sources_file) + + for lf in incl_licenses: + assert sources_lines.count(lf) == 1 + + for lf in excl_licenses: + assert sources_lines.count(lf) == 0 + + @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [ + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = + license_files = + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = + LICENSE-ABC + LICENSE-XYZ + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # license_file is still singular + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = LICENSE-ABC + license_files = + LICENSE-XYZ + LICENSE-PQR + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-PQR': DALS("PQR license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = LICENSE-ABC + license_files = + LICENSE-ABC + LICENSE-XYZ + LICENSE-PQR + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-PQR': DALS("PQR license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = LICENSE-ABC + license_files = + LICENSE-XYZ + """), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-PQR': DALS("PQR license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = LICENSE-ABC + license_files = + LICENSE-XYZ + LICENSE-PQR + """), + 'LICENSE-PQR': DALS("Test license") + }, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']), # with invalid licenses + ({ + 'setup.cfg': DALS(""" + [metadata] + license_file = LICENSE-ABC + license_files = + LICENSE-PQR + LICENSE-XYZ + """), + 'MANIFEST.in': DALS("exclude LICENSE-ABC\nexclude LICENSE-PQR"), + 'LICENSE-ABC': DALS("ABC license"), + 'LICENSE-PQR': DALS("PQR license"), + 'LICENSE-XYZ': DALS("XYZ license") + }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']) # manually excluded + ]) + def test_setup_cfg_license_file_license_files( + self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): + self._create_project() + build_files(files) + + environment.run_setup_py( + cmd=['egg_info'], + pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]) + ) + egg_info_dir = os.path.join('.', 'foo.egg-info') + + with open(os.path.join(egg_info_dir, 'SOURCES.txt')) as sources_file: + sources_lines = list(line.strip() for line in sources_file) + + for lf in incl_licenses: + assert sources_lines.count(lf) == 1 + + for lf in excl_licenses: + assert sources_lines.count(lf) == 0 + def test_long_description_content_type(self, tmpdir_cwd, env): # Test that specifying a `long_description_content_type` keyword arg to # the `setup` function results in writing a `Description-Content-Type` -- cgit v1.2.3 From 648dfe5afea3bf2a690c9267131a503bcd37d289 Mon Sep 17 00:00:00 2001 From: Mick Koch Date: Mon, 28 Oct 2019 18:38:30 -0400 Subject: Remove DALS for single-line strings --- setuptools/tests/test_egg_info.py | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 61da1bda..0db204ba 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -524,27 +524,27 @@ class TestEggInfo: [metadata] license_file = LICENSE """), - 'LICENSE': DALS("Test license") + 'LICENSE': "Test license" }, True), # with license ({ 'setup.cfg': DALS(""" [metadata] license_file = INVALID_LICENSE """), - 'LICENSE': DALS("Test license") + 'LICENSE': "Test license" }, False), # with an invalid license ({ 'setup.cfg': DALS(""" """), - 'LICENSE': DALS("Test license") + 'LICENSE': "Test license" }, False), # no license_file attribute ({ 'setup.cfg': DALS(""" [metadata] license_file = LICENSE """), - 'MANIFEST.in': DALS("exclude LICENSE"), - 'LICENSE': DALS("Test license") + 'MANIFEST.in': "exclude LICENSE", + 'LICENSE': "Test license" }, False) # license file is manually excluded ]) def test_setup_cfg_license_file( @@ -575,16 +575,16 @@ class TestEggInfo: LICENSE-ABC LICENSE-XYZ """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses ({ 'setup.cfg': DALS(""" [metadata] license_files = LICENSE-ABC, LICENSE-XYZ """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas ({ 'setup.cfg': DALS(""" @@ -592,24 +592,24 @@ class TestEggInfo: license_files = LICENSE-ABC """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license ({ 'setup.cfg': DALS(""" [metadata] license_files = """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty ({ 'setup.cfg': DALS(""" [metadata] license_files = LICENSE-XYZ """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line ({ 'setup.cfg': DALS(""" @@ -618,20 +618,20 @@ class TestEggInfo: LICENSE-ABC INVALID_LICENSE """), - 'LICENSE-ABC': DALS("Test license") + 'LICENSE-ABC': "Test license" }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license ({ 'setup.cfg': DALS(""" """), - 'LICENSE': DALS("Test license") + 'LICENSE': "Test license" }, [], ['LICENSE']), # no license_files attribute ({ 'setup.cfg': DALS(""" [metadata] license_files = LICENSE """), - 'MANIFEST.in': DALS("exclude LICENSE"), - 'LICENSE': DALS("Test license") + 'MANIFEST.in': "exclude LICENSE", + 'LICENSE': "Test license" }, [], ['LICENSE']), # license file is manually excluded ({ 'setup.cfg': DALS(""" @@ -640,9 +640,9 @@ class TestEggInfo: LICENSE-ABC LICENSE-XYZ """), - 'MANIFEST.in': DALS("exclude LICENSE-XYZ"), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'MANIFEST.in': "exclude LICENSE-XYZ", + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded ]) def test_setup_cfg_license_files( @@ -672,8 +672,8 @@ class TestEggInfo: license_file = license_files = """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty ({ 'setup.cfg': DALS(""" @@ -682,8 +682,8 @@ class TestEggInfo: LICENSE-ABC LICENSE-XYZ """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-XYZ': "XYZ license" }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # license_file is still singular ({ 'setup.cfg': DALS(""" @@ -693,9 +693,9 @@ class TestEggInfo: LICENSE-XYZ LICENSE-PQR """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-PQR': DALS("PQR license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-PQR': "PQR license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined ({ 'setup.cfg': DALS(""" @@ -706,9 +706,9 @@ class TestEggInfo: LICENSE-XYZ LICENSE-PQR """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-PQR': DALS("PQR license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-PQR': "PQR license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license ({ 'setup.cfg': DALS(""" @@ -717,9 +717,9 @@ class TestEggInfo: license_files = LICENSE-XYZ """), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-PQR': DALS("PQR license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'LICENSE-ABC': "ABC license", + 'LICENSE-PQR': "PQR license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset ({ 'setup.cfg': DALS(""" @@ -729,7 +729,7 @@ class TestEggInfo: LICENSE-XYZ LICENSE-PQR """), - 'LICENSE-PQR': DALS("Test license") + 'LICENSE-PQR': "Test license" }, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']), # with invalid licenses ({ 'setup.cfg': DALS(""" @@ -739,10 +739,10 @@ class TestEggInfo: LICENSE-PQR LICENSE-XYZ """), - 'MANIFEST.in': DALS("exclude LICENSE-ABC\nexclude LICENSE-PQR"), - 'LICENSE-ABC': DALS("ABC license"), - 'LICENSE-PQR': DALS("PQR license"), - 'LICENSE-XYZ': DALS("XYZ license") + 'MANIFEST.in': "exclude LICENSE-ABC\nexclude LICENSE-PQR", + 'LICENSE-ABC': "ABC license", + 'LICENSE-PQR': "PQR license", + 'LICENSE-XYZ': "XYZ license" }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']) # manually excluded ]) def test_setup_cfg_license_file_license_files( -- cgit v1.2.3 From 4a31168e517134529c229b310e89039323fdb02f Mon Sep 17 00:00:00 2001 From: Mick Koch Date: Mon, 28 Oct 2019 18:45:42 -0400 Subject: Use an OrderedSet for accumulating license files --- setuptools/command/sdist.py | 4 ++-- setuptools/dist.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 24316640..6043e0b9 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -5,7 +5,7 @@ import sys import io import contextlib -from setuptools.extern import six +from setuptools.extern import six, ordered_set from .py36compat import sdist_add_defaults @@ -204,7 +204,7 @@ class sdist(sdist_add_defaults, orig.sdist): valid paths to 'self.filelist'. """ - files = set() + files = ordered_set.OrderedSet() opts = self.distribution.get_option_dict('metadata') diff --git a/setuptools/dist.py b/setuptools/dist.py index fb379a20..0f3f7322 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -409,7 +409,7 @@ class Distribution(_Distribution): 'long_description_content_type': None, 'project_urls': dict, 'provides_extras': ordered_set.OrderedSet, - 'license_files': list, + 'license_files': ordered_set.OrderedSet, } _patched_dist = None -- cgit v1.2.3 From e08ec2b640f6b2bf943fea70e2a7f9881bbe6e91 Mon Sep 17 00:00:00 2001 From: Mick Koch Date: Mon, 28 Oct 2019 19:16:13 -0400 Subject: Filter out missing files and use extend() --- setuptools/command/sdist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 6043e0b9..55ecdd97 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -226,6 +226,6 @@ class sdist(sdist_add_defaults, orig.sdist): log.warn( "warning: Failed to find the configured license file '%s'", f) - continue + files.remove(f) - self.filelist.append(f) + self.filelist.extend(files) -- cgit v1.2.3 From 3a0520b43dfac9f6ba507c6d09a60290219a0802 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 28 Oct 2019 21:52:40 -0400 Subject: Extract compatibility function into compatibility module. --- setuptools/_imp.py | 10 +++------- setuptools/py34compat.py | 8 ++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 setuptools/py34compat.py diff --git a/setuptools/_imp.py b/setuptools/_imp.py index 49ddc852..ee719c9a 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -8,6 +8,8 @@ import sys import importlib.util import importlib.machinery +from .py34compat import module_from_spec + PY_SOURCE = 1 PY_COMPILED = 2 @@ -63,12 +65,6 @@ def get_frozen_object(module, paths): return spec.loader.get_code(_resolve(module)) -def _module_from_spec(spec): - if sys.version_info >= (3, 5): - return importlib.util.module_from_spec(spec) - else: - return spec.loader.load_module(spec.name) - def get_module(module, paths, info): spec = importlib.util.find_spec(module, paths) - return _module_from_spec(spec) + return module_from_spec(spec) diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py new file mode 100644 index 00000000..bc7eefa9 --- /dev/null +++ b/setuptools/py34compat.py @@ -0,0 +1,8 @@ +import importlib.util + + +try: + module_from_spec = importlib.util.module_from_spec +except AttributeError: + def module_from_spec(spec): + return spec.loader.load_module(spec.name) -- cgit v1.2.3 From 2175d6bdcf4fe626e713961bb0315c91f206746b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 28 Oct 2019 21:57:31 -0400 Subject: Add changelog entry. --- changelog.d/479.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/479.bugfix.rst diff --git a/changelog.d/479.bugfix.rst b/changelog.d/479.bugfix.rst new file mode 100644 index 00000000..3c33964e --- /dev/null +++ b/changelog.d/479.bugfix.rst @@ -0,0 +1 @@ +Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. -- cgit v1.2.3 From e1f340b53f0088993b16e19999a4d6b0e86a9991 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 28 Oct 2019 22:01:34 -0400 Subject: Avoid importerror on older Pythons --- setuptools/py34compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py index bc7eefa9..54157a63 100644 --- a/setuptools/py34compat.py +++ b/setuptools/py34compat.py @@ -1,4 +1,4 @@ -import importlib.util +import importlib try: -- cgit v1.2.3 From 82689e1aa8e6548f26f2ce3bcd069411cb39bfcf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 28 Oct 2019 22:06:44 -0400 Subject: Ensure importlib.util is imported on Python 3.5 --- setuptools/py34compat.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py index 54157a63..3ad91722 100644 --- a/setuptools/py34compat.py +++ b/setuptools/py34compat.py @@ -1,5 +1,10 @@ import importlib +try: + import importlib.util +except ImportError: + pass + try: module_from_spec = importlib.util.module_from_spec -- cgit v1.2.3 From 20d6407aa5f68dbeba61e8967290f2fbde4f85ab Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Tue, 29 Oct 2019 10:51:54 +0300 Subject: Allow calling get_frozen_object without paths, raise ImportError when it cant find module --- setuptools/_imp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index ee719c9a..ab29ef21 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -60,11 +60,15 @@ def find_module(module, paths=None): return file, path, (suffix, mode, kind) -def get_frozen_object(module, paths): +def get_frozen_object(module, paths=None): spec = importlib.util.find_spec(module, paths) - return spec.loader.get_code(_resolve(module)) + if not spec: + raise ImportError("Can't find %s" % module) + return spec.loader.get_code(module) def get_module(module, paths, info): spec = importlib.util.find_spec(module, paths) + if not spec: + raise ImportError("Can't find %s" % module) return module_from_spec(spec) -- cgit v1.2.3 From cfa9245a7dea8a35f11580c0bfd27472a3182c7e Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Tue, 29 Oct 2019 10:53:40 +0300 Subject: Remove 'sys' import --- setuptools/_imp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index ab29ef21..a3cce9b2 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -4,7 +4,6 @@ from the deprecated imp module. """ import os -import sys import importlib.util import importlib.machinery -- cgit v1.2.3 From 6defe6b8fd6008f61ce5ecfae91dc1df5e123694 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 29 Oct 2019 09:52:32 -0400 Subject: Rename changelog file --- changelog.d/479.bugfix.rst | 1 - changelog.d/479.change.rst | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 changelog.d/479.bugfix.rst create mode 100644 changelog.d/479.change.rst diff --git a/changelog.d/479.bugfix.rst b/changelog.d/479.bugfix.rst deleted file mode 100644 index 3c33964e..00000000 --- a/changelog.d/479.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. diff --git a/changelog.d/479.change.rst b/changelog.d/479.change.rst new file mode 100644 index 00000000..3c33964e --- /dev/null +++ b/changelog.d/479.change.rst @@ -0,0 +1 @@ +Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. -- cgit v1.2.3 From a0fe403c141369defacf12dccbdc01634bbcb1da Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 29 Oct 2019 09:53:23 -0400 Subject: =?UTF-8?q?Bump=20version:=2041.5.1=20=E2=86=92=2041.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 6 ++++++ changelog.d/479.change.rst | 1 - setup.cfg | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/479.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0dc75e9b..40db5b03 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.5.1 +current_version = 41.6.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index bac35638..ba7b4647 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v41.6.0 +------- + +* #479: Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. + + v41.5.1 ------- diff --git a/changelog.d/479.change.rst b/changelog.d/479.change.rst deleted file mode 100644 index 3c33964e..00000000 --- a/changelog.d/479.change.rst +++ /dev/null @@ -1 +0,0 @@ -Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``. diff --git a/setup.cfg b/setup.cfg index 8038b463..42a3d86c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.5.1 +version = 41.6.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 9fd54518fcb660d9d3f92b1bb242082f20c69c1f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 31 Oct 2019 15:00:55 -0400 Subject: Suppress deprecation of bdist_wininst. Ref #1823. --- pytest.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytest.ini b/pytest.ini index 612fb91f..549e4d68 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,6 @@ flake8-ignore = setuptools/site-patch.py F821 setuptools/py*compat.py F811 doctest_optionflags=ELLIPSIS ALLOW_UNICODE +filterwarnings = + # https://github.com/pypa/setuptools/issues/1823 + ignore:distutils.command.bdist_wininst::command is deprecated -- cgit v1.2.3 From 6ac7b4ee036ef8e6954198689d214e8ee9b29118 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 31 Oct 2019 15:58:45 -0400 Subject: Suppress deprecation of bdist_wininst (redo). Ref #1823. --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 549e4d68..0370f7f8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,4 +7,4 @@ flake8-ignore = doctest_optionflags=ELLIPSIS ALLOW_UNICODE filterwarnings = # https://github.com/pypa/setuptools/issues/1823 - ignore:distutils.command.bdist_wininst::command is deprecated + ignore:bdist_wininst command is deprecated -- cgit v1.2.3 From f413f95e95b34b26d9ed9d9c43b3e4b3d30caecc Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Thu, 31 Oct 2019 11:25:57 -0400 Subject: Remove "upload" and "register" commands. The upload and register commands were deprecated over a year ago, in July 2018 (PR GH-1410, discussed in issue GH-1381). It is time to actively remove them in favor of twine. --- changelog.d/1898.breaking.rst | 1 + docs/setuptools.txt | 15 ++- setuptools/command/__init__.py | 3 +- setuptools/command/register.py | 22 ++-- setuptools/command/upload.py | 195 ++--------------------------------- setuptools/errors.py | 16 +++ setuptools/tests/test_register.py | 43 ++------ setuptools/tests/test_upload.py | 211 ++------------------------------------ 8 files changed, 64 insertions(+), 442 deletions(-) create mode 100644 changelog.d/1898.breaking.rst create mode 100644 setuptools/errors.py diff --git a/changelog.d/1898.breaking.rst b/changelog.d/1898.breaking.rst new file mode 100644 index 00000000..844a8a42 --- /dev/null +++ b/changelog.d/1898.breaking.rst @@ -0,0 +1 @@ +Removed the "upload" and "register" commands in favor of `twine `_. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 344ea5bc..399a56d3 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -2087,16 +2087,13 @@ New in 41.5.0: Deprecated the test command. ``upload`` - Upload source and/or egg distributions to PyPI =========================================================== -.. warning:: - **upload** is deprecated in favor of using `twine - `_ - -The ``upload`` command is implemented and `documented -`_ -in distutils. +The ``upload`` command was deprecated in version 40.0 and removed in version +42.0. Use `twine `_ instead. -New in 20.1: Added keyring support. -New in 40.0: Deprecated the upload command. +For more information on the current best practices in uploading your packages +to PyPI, see the Python Packaging User Guide's "Packaging Python Projects" +tutorial specifically the section on `uploading the distribution archives +`_. ----------------------------------------- diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py index fe619e2e..743f5588 100644 --- a/setuptools/command/__init__.py +++ b/setuptools/command/__init__.py @@ -2,8 +2,7 @@ __all__ = [ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', - 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', - 'dist_info', + 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info', ] from distutils.command.bdist import bdist diff --git a/setuptools/command/register.py b/setuptools/command/register.py index 98bc0156..b8266b9a 100644 --- a/setuptools/command/register.py +++ b/setuptools/command/register.py @@ -1,18 +1,18 @@ from distutils import log import distutils.command.register as orig +from setuptools.errors import RemovedCommandError + class register(orig.register): - __doc__ = orig.register.__doc__ + """Formerly used to register packages on PyPI.""" def run(self): - try: - # Make sure that we are using valid current name/version info - self.run_command('egg_info') - orig.register.run(self) - finally: - self.announce( - "WARNING: Registering is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) + msg = ( + "The register command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" + ) + + self.announce("ERROR: " + msg, log.ERROR) + + raise RemovedCommandError(msg) diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 6db8888b..ec7f81e2 100644 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -1,196 +1,17 @@ -import io -import os -import hashlib -import getpass - -from base64 import standard_b64encode - from distutils import log from distutils.command import upload as orig -from distutils.spawn import spawn - -from distutils.errors import DistutilsError -from setuptools.extern.six.moves.urllib.request import urlopen, Request -from setuptools.extern.six.moves.urllib.error import HTTPError -from setuptools.extern.six.moves.urllib.parse import urlparse +from setuptools.errors import RemovedCommandError class upload(orig.upload): - """ - Override default upload behavior to obtain password - in a variety of different ways. - """ - def run(self): - try: - orig.upload.run(self) - finally: - self.announce( - "WARNING: Uploading via this command is deprecated, use twine " - "to upload instead (https://pypi.org/p/twine/)", - log.WARN - ) + """Formerly used to upload packages to PyPI.""" - def finalize_options(self): - orig.upload.finalize_options(self) - self.username = ( - self.username or - getpass.getuser() - ) - # Attempt to obtain password. Short circuit evaluation at the first - # sign of success. - self.password = ( - self.password or - self._load_password_from_keyring() or - self._prompt_for_password() + def run(self): + msg = ( + "The upload command has been removed, use twine to upload " + + "instead (https://pypi.org/p/twine)" ) - def upload_file(self, command, pyversion, filename): - # Makes sure the repository URL is compliant - schema, netloc, url, params, query, fragments = \ - urlparse(self.repository) - if params or query or fragments: - raise AssertionError("Incompatible url %s" % self.repository) - - if schema not in ('http', 'https'): - raise AssertionError("unsupported schema " + schema) - - # Sign if requested - if self.sign: - gpg_args = ["gpg", "--detach-sign", "-a", filename] - if self.identity: - gpg_args[2:2] = ["--local-user", self.identity] - spawn(gpg_args, - dry_run=self.dry_run) - - # Fill in the data - send all the meta-data in case we need to - # register a new release - with open(filename, 'rb') as f: - content = f.read() - - meta = self.distribution.metadata - - data = { - # action - ':action': 'file_upload', - 'protocol_version': '1', - - # identify release - 'name': meta.get_name(), - 'version': meta.get_version(), - - # file content - 'content': (os.path.basename(filename), content), - 'filetype': command, - 'pyversion': pyversion, - 'md5_digest': hashlib.md5(content).hexdigest(), - - # additional meta-data - 'metadata_version': str(meta.get_metadata_version()), - 'summary': meta.get_description(), - 'home_page': meta.get_url(), - 'author': meta.get_contact(), - 'author_email': meta.get_contact_email(), - 'license': meta.get_licence(), - 'description': meta.get_long_description(), - 'keywords': meta.get_keywords(), - 'platform': meta.get_platforms(), - 'classifiers': meta.get_classifiers(), - 'download_url': meta.get_download_url(), - # PEP 314 - 'provides': meta.get_provides(), - 'requires': meta.get_requires(), - 'obsoletes': meta.get_obsoletes(), - } - - data['comment'] = '' - - if self.sign: - data['gpg_signature'] = (os.path.basename(filename) + ".asc", - open(filename+".asc", "rb").read()) - - # set up the authentication - user_pass = (self.username + ":" + self.password).encode('ascii') - # The exact encoding of the authentication string is debated. - # Anyway PyPI only accepts ascii for both username or password. - auth = "Basic " + standard_b64encode(user_pass).decode('ascii') - - # Build up the MIME payload for the POST data - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\r\n--' + boundary.encode('ascii') - end_boundary = sep_boundary + b'--\r\n' - body = io.BytesIO() - for key, value in data.items(): - title = '\r\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(value, list): - value = [value] - for value in value: - if type(value) is tuple: - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = str(value).encode('utf-8') - body.write(sep_boundary) - body.write(title.encode('utf-8')) - body.write(b"\r\n\r\n") - body.write(value) - body.write(end_boundary) - body = body.getvalue() - - msg = "Submitting %s to %s" % (filename, self.repository) - self.announce(msg, log.INFO) - - # build the Request - headers = { - 'Content-type': 'multipart/form-data; boundary=%s' % boundary, - 'Content-length': str(len(body)), - 'Authorization': auth, - } - - request = Request(self.repository, data=body, - headers=headers) - # send the data - try: - result = urlopen(request) - status = result.getcode() - reason = result.msg - except HTTPError as e: - status = e.code - reason = e.msg - except OSError as e: - self.announce(str(e), log.ERROR) - raise - - if status == 200: - self.announce('Server response (%s): %s' % (status, reason), - log.INFO) - if self.show_response: - text = getattr(self, '_read_pypi_response', - lambda x: None)(result) - if text is not None: - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (status, reason) - self.announce(msg, log.ERROR) - raise DistutilsError(msg) - - def _load_password_from_keyring(self): - """ - Attempt to load password from keyring. Suppress Exceptions. - """ - try: - keyring = __import__('keyring') - return keyring.get_password(self.repository, self.username) - except Exception: - pass - - def _prompt_for_password(self): - """ - Prompt for a password on the tty. Suppress Exceptions. - """ - try: - return getpass.getpass() - except (Exception, KeyboardInterrupt): - pass + self.announce("ERROR: " + msg, log.ERROR) + raise RemovedCommandError(msg) diff --git a/setuptools/errors.py b/setuptools/errors.py new file mode 100644 index 00000000..2701747f --- /dev/null +++ b/setuptools/errors.py @@ -0,0 +1,16 @@ +"""setuptools.errors + +Provides exceptions used by setuptools modules. +""" + +from distutils.errors import DistutilsError + + +class RemovedCommandError(DistutilsError, RuntimeError): + """Error used for commands that have been removed in setuptools. + + Since ``setuptools`` is built on ``distutils``, simply removing a command + from ``setuptools`` will make the behavior fall back to ``distutils``; this + error is raised if a command exists in ``distutils`` but has been actively + removed in ``setuptools``. + """ diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py index 96114595..98605806 100644 --- a/setuptools/tests/test_register.py +++ b/setuptools/tests/test_register.py @@ -1,43 +1,22 @@ -import mock -from distutils import log - -import pytest - from setuptools.command.register import register from setuptools.dist import Distribution +from setuptools.errors import RemovedCommandError +try: + from unittest import mock +except ImportError: + import mock -class TestRegisterTest: - def test_warns_deprecation(self): - dist = Distribution() - - cmd = register(dist) - cmd.run_command = mock.Mock() - cmd.send_metadata = mock.Mock() - cmd.announce = mock.Mock() - - cmd.run() +import pytest - cmd.announce.assert_called_with( - "WARNING: Registering is deprecated, use twine to upload instead " - "(https://pypi.org/p/twine/)", - log.WARN - ) - def test_warns_deprecation_when_raising(self): +class TestRegister: + def test_register_exception(self): + """Ensure that the register command has been properly removed.""" dist = Distribution() + dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] cmd = register(dist) - cmd.run_command = mock.Mock() - cmd.send_metadata = mock.Mock() - cmd.send_metadata.side_effect = Exception - cmd.announce = mock.Mock() - with pytest.raises(Exception): + with pytest.raises(RemovedCommandError): cmd.run() - - cmd.announce.assert_called_with( - "WARNING: Registering is deprecated, use twine to upload instead " - "(https://pypi.org/p/twine/)", - log.WARN - ) diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py index 320c6959..7586cb26 100644 --- a/setuptools/tests/test_upload.py +++ b/setuptools/tests/test_upload.py @@ -1,213 +1,22 @@ -import mock -import os -import re - -from distutils import log -from distutils.errors import DistutilsError - -import pytest - from setuptools.command.upload import upload from setuptools.dist import Distribution -from setuptools.extern import six - - -def _parse_upload_body(body): - boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - entries = [] - name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"') - - for entry in body.split(boundary): - pair = entry.split(u'\r\n\r\n') - if not len(pair) == 2: - continue - - key, value = map(six.text_type.strip, pair) - m = name_re.match(key) - if m is not None: - key = m.group(1) - - entries.append((key, value)) - - return entries - - -@pytest.fixture -def patched_upload(tmpdir): - class Fix: - def __init__(self, cmd, urlopen): - self.cmd = cmd - self.urlopen = urlopen - - def __iter__(self): - return iter((self.cmd, self.urlopen)) - - def get_uploaded_metadata(self): - request = self.urlopen.call_args_list[0][0][0] - body = request.data.decode('utf-8') - entries = dict(_parse_upload_body(body)) - - return entries +from setuptools.errors import RemovedCommandError - class ResponseMock(mock.Mock): - def getheader(self, name, default=None): - """Mocked getheader method for response object""" - return { - 'content-type': 'text/plain; charset=utf-8', - }.get(name.lower(), default) +try: + from unittest import mock +except ImportError: + import mock - with mock.patch('setuptools.command.upload.urlopen') as urlopen: - urlopen.return_value = ResponseMock() - urlopen.return_value.getcode.return_value = 200 - urlopen.return_value.read.return_value = b'' - - content = os.path.join(str(tmpdir), "content_data") - - with open(content, 'w') as f: - f.write("Some content") - - dist = Distribution() - dist.dist_files = [('sdist', '3.7.0', content)] - - cmd = upload(dist) - cmd.announce = mock.Mock() - cmd.username = 'user' - cmd.password = 'hunter2' - - yield Fix(cmd, urlopen) - - -class TestUploadTest: - def test_upload_metadata(self, patched_upload): - cmd, patch = patched_upload - - # Set the metadata version to 2.1 - cmd.distribution.metadata.metadata_version = '2.1' - - # Run the command - cmd.ensure_finalized() - cmd.run() - - # Make sure we did the upload - patch.assert_called_once() - - # Make sure the metadata version is correct in the headers - entries = patched_upload.get_uploaded_metadata() - assert entries['metadata_version'] == '2.1' - - def test_warns_deprecation(self): - dist = Distribution() - dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] - - cmd = upload(dist) - cmd.upload_file = mock.Mock() - cmd.announce = mock.Mock() - - cmd.run() +import pytest - cmd.announce.assert_called_once_with( - "WARNING: Uploading via this command is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) - def test_warns_deprecation_when_raising(self): +class TestUpload: + def test_upload_exception(self): + """Ensure that the register command has been properly removed.""" dist = Distribution() dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())] cmd = upload(dist) - cmd.upload_file = mock.Mock() - cmd.upload_file.side_effect = Exception - cmd.announce = mock.Mock() - - with pytest.raises(Exception): - cmd.run() - - cmd.announce.assert_called_once_with( - "WARNING: Uploading via this command is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) - - @pytest.mark.parametrize('url', [ - 'https://example.com/a;parameter', # Has parameters - 'https://example.com/a?query', # Has query - 'https://example.com/a#fragment', # Has fragment - 'ftp://example.com', # Invalid scheme - - ]) - def test_upload_file_invalid_url(self, url, patched_upload): - patched_upload.urlopen.side_effect = Exception("Should not be reached") - - cmd = patched_upload.cmd - cmd.repository = url - - cmd.ensure_finalized() - with pytest.raises(AssertionError): - cmd.run() - - def test_upload_file_http_error(self, patched_upload): - patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError( - 'https://example.com', - 404, - 'File not found', - None, - None - ) - - cmd = patched_upload.cmd - cmd.ensure_finalized() - with pytest.raises(DistutilsError): + with pytest.raises(RemovedCommandError): cmd.run() - - cmd.announce.assert_any_call( - 'Upload failed (404): File not found', - log.ERROR) - - def test_upload_file_os_error(self, patched_upload): - patched_upload.urlopen.side_effect = OSError("Invalid") - - cmd = patched_upload.cmd - cmd.ensure_finalized() - - with pytest.raises(OSError): - cmd.run() - - cmd.announce.assert_any_call('Invalid', log.ERROR) - - @mock.patch('setuptools.command.upload.spawn') - def test_upload_file_gpg(self, spawn, patched_upload): - cmd, urlopen = patched_upload - - cmd.sign = True - cmd.identity = "Alice" - cmd.dry_run = True - content_fname = cmd.distribution.dist_files[0][2] - signed_file = content_fname + '.asc' - - with open(signed_file, 'wb') as f: - f.write("signed-data".encode('utf-8')) - - cmd.ensure_finalized() - cmd.run() - - # Make sure that GPG was called - spawn.assert_called_once_with([ - "gpg", "--detach-sign", "--local-user", "Alice", "-a", - content_fname - ], dry_run=True) - - # Read the 'signed' data that was transmitted - entries = patched_upload.get_uploaded_metadata() - assert entries['gpg_signature'] == 'signed-data' - - def test_show_response_no_error(self, patched_upload): - # This test is just that show_response doesn't throw an error - # It is not really important what the printed response looks like - # in a deprecated command, but we don't want to introduce new - # errors when importing this function from distutils - - patched_upload.cmd.show_response = True - patched_upload.cmd.ensure_finalized() - patched_upload.cmd.run() -- cgit v1.2.3 From 14c82188dae748fb7a7dd126fb2a5553e4865c95 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 8 Oct 2019 12:18:31 +0200 Subject: test: drop pkg_resources tests dependency on easy_install --- .../tests/data/my-test-package-source/setup.cfg | 0 .../tests/data/my-test-package-source/setup.py | 6 +++ .../EGG-INFO/PKG-INFO | 10 ++++ .../EGG-INFO/SOURCES.txt | 7 +++ .../EGG-INFO/dependency_links.txt | 1 + .../EGG-INFO/top_level.txt | 1 + .../EGG-INFO/zip-safe | 1 + .../my_test_package-1.0-py3.7.egg | Bin 0 -> 843 bytes pkg_resources/tests/test_find_distributions.py | 58 +++++---------------- pytest.ini | 2 +- 10 files changed, 40 insertions(+), 46 deletions(-) create mode 100644 pkg_resources/tests/data/my-test-package-source/setup.cfg create mode 100644 pkg_resources/tests/data/my-test-package-source/setup.py create mode 100644 pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO create mode 100644 pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt create mode 100644 pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt create mode 100644 pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt create mode 100644 pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe create mode 100644 pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg diff --git a/pkg_resources/tests/data/my-test-package-source/setup.cfg b/pkg_resources/tests/data/my-test-package-source/setup.cfg new file mode 100644 index 00000000..e69de29b diff --git a/pkg_resources/tests/data/my-test-package-source/setup.py b/pkg_resources/tests/data/my-test-package-source/setup.py new file mode 100644 index 00000000..fe80d28f --- /dev/null +++ b/pkg_resources/tests/data/my-test-package-source/setup.py @@ -0,0 +1,6 @@ +import setuptools +setuptools.setup( + name="my-test-package", + version="1.0", + zip_safe=True, +) diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO new file mode 100644 index 00000000..7328e3f7 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: my-test-package +Version: 1.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 00000000..3c4ee167 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt @@ -0,0 +1,7 @@ +setup.cfg +setup.py +my_test_package.egg-info/PKG-INFO +my_test_package.egg-info/SOURCES.txt +my_test_package.egg-info/dependency_links.txt +my_test_package.egg-info/top_level.txt +my_test_package.egg-info/zip-safe \ No newline at end of file diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe @@ -0,0 +1 @@ + diff --git a/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg new file mode 100644 index 00000000..5115b895 Binary files /dev/null and b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg differ diff --git a/pkg_resources/tests/test_find_distributions.py b/pkg_resources/tests/test_find_distributions.py index d735c590..f9594422 100644 --- a/pkg_resources/tests/test_find_distributions.py +++ b/pkg_resources/tests/test_find_distributions.py @@ -1,17 +1,9 @@ -import subprocess -import sys - +import py import pytest import pkg_resources -SETUP_TEMPLATE = """ -import setuptools -setuptools.setup( - name="my-test-package", - version="1.0", - zip_safe=True, -) -""".lstrip() + +TESTS_DATA_DIR = py.path.local(__file__).dirpath('data') class TestFindDistributions: @@ -21,46 +13,22 @@ class TestFindDistributions: target_dir = tmpdir.mkdir('target') # place a .egg named directory in the target that is not an egg: target_dir.mkdir('not.an.egg') - return str(target_dir) - - @pytest.fixture - def project_dir(self, tmpdir): - project_dir = tmpdir.mkdir('my-test-package') - (project_dir / "setup.py").write(SETUP_TEMPLATE) - return str(project_dir) + return target_dir def test_non_egg_dir_named_egg(self, target_dir): - dists = pkg_resources.find_distributions(target_dir) + dists = pkg_resources.find_distributions(str(target_dir)) assert not list(dists) - def test_standalone_egg_directory(self, project_dir, target_dir): - # install this distro as an unpacked egg: - args = [ - sys.executable, - '-c', 'from setuptools.command.easy_install import main; main()', - '-mNx', - '-d', target_dir, - '--always-unzip', - project_dir, - ] - subprocess.check_call(args) - dists = pkg_resources.find_distributions(target_dir) + def test_standalone_egg_directory(self, target_dir): + (TESTS_DATA_DIR / 'my-test-package_unpacked-egg').copy(target_dir) + dists = pkg_resources.find_distributions(str(target_dir)) assert [dist.project_name for dist in dists] == ['my-test-package'] - dists = pkg_resources.find_distributions(target_dir, only=True) + dists = pkg_resources.find_distributions(str(target_dir), only=True) assert not list(dists) - def test_zipped_egg(self, project_dir, target_dir): - # install this distro as an unpacked egg: - args = [ - sys.executable, - '-c', 'from setuptools.command.easy_install import main; main()', - '-mNx', - '-d', target_dir, - '--zip-ok', - project_dir, - ] - subprocess.check_call(args) - dists = pkg_resources.find_distributions(target_dir) + def test_zipped_egg(self, target_dir): + (TESTS_DATA_DIR / 'my-test-package_zipped-egg').copy(target_dir) + dists = pkg_resources.find_distributions(str(target_dir)) assert [dist.project_name for dist in dists] == ['my-test-package'] - dists = pkg_resources.find_distributions(target_dir, only=True) + dists = pkg_resources.find_distributions(str(target_dir), only=True) assert not list(dists) diff --git a/pytest.ini b/pytest.ini index 5886973b..0bc1ec01 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX -norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern tools .* +norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .* flake8-ignore = setuptools/site-patch.py F821 setuptools/py*compat.py F811 -- cgit v1.2.3 From bbf825eee764cae0bc44077ccc957a733d53d095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20S=C3=BAkup?= Date: Fri, 15 Nov 2019 08:52:35 +0100 Subject: Fix _imp module behaviour if is defined paths in find_spec call fixes #1896 --- setuptools/_imp.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index a3cce9b2..6ccec579 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -19,7 +19,10 @@ PY_FROZEN = 7 def find_module(module, paths=None): """Just like 'imp.find_module()', but with package support""" - spec = importlib.util.find_spec(module, paths) + if isinstance(paths, list): + spec = importlib.machinery.PathFinder().find_spec(module, paths) + else: + spec = importlib.util.find_spec(module, paths) if spec is None: raise ImportError("Can't find %s" % module) if not spec.has_location and hasattr(spec, 'submodule_search_locations'): @@ -60,14 +63,20 @@ def find_module(module, paths=None): def get_frozen_object(module, paths=None): - spec = importlib.util.find_spec(module, paths) + if isinstance(paths, list): + spec = importlib.machinery.PathFinder().find_spec(module, paths) + else: + spec = importlib.util.find_spec(module, paths) if not spec: raise ImportError("Can't find %s" % module) return spec.loader.get_code(module) def get_module(module, paths, info): - spec = importlib.util.find_spec(module, paths) + if isinstance(paths, list): + spec = importlib.machinery.PathFinder().find_spec(module, paths) + else: + spec = importlib.util.find_spec(module, paths) if not spec: raise ImportError("Can't find %s" % module) return module_from_spec(spec) -- cgit v1.2.3 From fed59d837495208c13cec64b5394cdd2cc3cb6de Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Thu, 14 Nov 2019 19:09:20 +0100 Subject: tests: fix some pytest warnings under Python 2 --- setuptools/tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py index 1c0b2b18..f1a27f8b 100644 --- a/setuptools/tests/test_integration.py +++ b/setuptools/tests/test_integration.py @@ -64,7 +64,7 @@ def install_context(request, tmpdir, monkeypatch): monkeypatch.setattr('site.USER_BASE', user_base.strpath) monkeypatch.setattr('site.USER_SITE', user_site.strpath) monkeypatch.setattr('sys.path', sys.path + [install_dir.strpath]) - monkeypatch.setenv('PYTHONPATH', os.path.pathsep.join(sys.path)) + monkeypatch.setenv(str('PYTHONPATH'), str(os.path.pathsep.join(sys.path))) # Set up the command for performing the installation. dist = Distribution() -- cgit v1.2.3 From 77fa2369892b7e45ede9cad18aaa3f0721c96cc3 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 15 Nov 2019 19:06:26 +0100 Subject: tweak workaround for #1644 Work around buggy pip detection code for "pip.exe install/update pip ...". --- tools/tox_pip.py | 11 ++++++++++- tox.ini | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 1117f996..5aeca805 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -17,12 +17,21 @@ def pip(args): 'pip']) shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0]) # And use that version. + pypath = os.environ.get('PYTHONPATH') + pypath = pypath.split(os.pathsep) if pypath is not None else [] + pypath.insert(0, TOX_PIP_DIR) + os.environ['PYTHONPATH'] = os.pathsep.join(pypath) + # Disable PEP 517 support when using editable installs. for n, a in enumerate(args): if not a.startswith('-'): if a in 'install' and '-e' in args[n:]: args.insert(n + 1, '--no-use-pep517') break - subprocess.check_call([sys.executable, os.path.join(TOX_PIP_DIR, 'pip')] + args) + # Fix call for setuptools editable install. + for n, a in enumerate(args): + if a == '.': + args[n] = os.getcwd() + subprocess.check_call([sys.executable, '-m', 'pip'] + args, cwd=TOX_PIP_DIR) if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index 8b34c235..5d439cb3 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ envlist=python pip = python {toxinidir}/tools/tox_pip.py [testenv] -deps=-rtests/requirements.txt +deps=-r{toxinidir}/tests/requirements.txt install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} -- cgit v1.2.3 From d6948c636f5e657ac56911b71b7a459d326d8389 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sun, 29 Apr 2018 19:47:42 +0200 Subject: dist: re-implement `fetch_build_egg` to use pip --- docs/setuptools.txt | 17 +-- setuptools/dist.py | 28 +---- setuptools/installer.py | 129 ++++++++++++++++++++++ setuptools/tests/server.py | 19 +++- setuptools/tests/test_easy_install.py | 197 ++++++++++++++++++++++++++++------ setuptools/tests/test_virtualenv.py | 18 ++-- tests/requirements.txt | 1 + 7 files changed, 336 insertions(+), 73 deletions(-) create mode 100644 setuptools/installer.py diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 399a56d3..9c8821dc 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -282,10 +282,11 @@ unless you need the associated ``setuptools`` feature. ``setup_requires`` A string or list of strings specifying what other distributions need to be present in order for the *setup script* to run. ``setuptools`` will - attempt to obtain these before processing the rest of the setup script or - commands. This argument is needed if you are using distutils extensions as - part of your build process; for example, extensions that process setup() - arguments and turn them into EGG-INFO metadata files. + attempt to obtain these (using pip if available) before processing the + rest of the setup script or commands. This argument is needed if you + are using distutils extensions as part of your build process; for + example, extensions that process setup() arguments and turn them into + EGG-INFO metadata files. (Note: projects listed in ``setup_requires`` will NOT be automatically installed on the system where the setup script is being run. They are @@ -332,10 +333,10 @@ unless you need the associated ``setuptools`` feature. needed to install it, you can use this option to specify them. It should be a string or list of strings specifying what other distributions need to be present for the package's tests to run. When you run the ``test`` - command, ``setuptools`` will attempt to obtain these. Note that these - required projects will *not* be installed on the system where the tests - are run, but only downloaded to the project's setup directory if they're - not already installed locally. + command, ``setuptools`` will attempt to obtain these (using pip if + available). Note that these required projects will *not* be installed on + the system where the tests are run, but only downloaded to the project's setup + directory if they're not already installed locally. New in 41.5.0: Deprecated the test command. diff --git a/setuptools/dist.py b/setuptools/dist.py index 2e5ad4bd..4a76b52b 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -759,32 +759,8 @@ class Distribution(_Distribution): def fetch_build_egg(self, req): """Fetch an egg needed for building""" - from setuptools.command.easy_install import easy_install - dist = self.__class__({'script_args': ['easy_install']}) - opts = dist.get_option_dict('easy_install') - opts.clear() - opts.update( - (k, v) - for k, v in self.get_option_dict('easy_install').items() - if k in ( - # don't use any other settings - 'find_links', 'site_dirs', 'index_url', - 'optimize', 'site_dirs', 'allow_hosts', - )) - if self.dependency_links: - links = self.dependency_links[:] - if 'find_links' in opts: - links = opts['find_links'][1] + links - opts['find_links'] = ('setup', links) - install_dir = self.get_egg_cache_dir() - cmd = easy_install( - dist, args=["x"], install_dir=install_dir, - exclude_scripts=True, - always_copy=False, build_directory=None, editable=False, - upgrade=False, multi_version=True, no_report=True, user=False - ) - cmd.ensure_finalized() - return cmd.easy_install(req) + from setuptools.installer import fetch_build_egg + return fetch_build_egg(self, req) def _set_global_opts_from_features(self): """Add --with-X/--without-X options based on optional features""" diff --git a/setuptools/installer.py b/setuptools/installer.py new file mode 100644 index 00000000..35bc3cc5 --- /dev/null +++ b/setuptools/installer.py @@ -0,0 +1,129 @@ +import glob +import os +import subprocess +import sys +from distutils import log +from distutils.errors import DistutilsError + +import pkg_resources +from setuptools.command.easy_install import easy_install +from setuptools.wheel import Wheel + +from .py31compat import TemporaryDirectory + + +def _legacy_fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Legacy path using EasyInstall. + """ + tmp_dist = dist.__class__({'script_args': ['easy_install']}) + opts = tmp_dist.get_option_dict('easy_install') + opts.clear() + opts.update( + (k, v) + for k, v in dist.get_option_dict('easy_install').items() + if k in ( + # don't use any other settings + 'find_links', 'site_dirs', 'index_url', + 'optimize', 'site_dirs', 'allow_hosts', + )) + if dist.dependency_links: + links = dist.dependency_links[:] + if 'find_links' in opts: + links = opts['find_links'][1] + links + opts['find_links'] = ('setup', links) + install_dir = dist.get_egg_cache_dir() + cmd = easy_install( + tmp_dist, args=["x"], install_dir=install_dir, + exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False, multi_version=True, no_report=True, user=False + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + +def fetch_build_egg(dist, req): + """Fetch an egg needed for building. + + Use pip/wheel to fetch/build a wheel.""" + # Check pip is available. + try: + pkg_resources.get_distribution('pip') + except pkg_resources.DistributionNotFound: + dist.announce( + 'WARNING: The pip package is not available, falling back ' + 'to EasyInstall for handling setup_requires/test_requires; ' + 'this is deprecated and will be removed in a future version.' + , log.WARN + ) + return _legacy_fetch_build_egg(dist, req) + # Warn if wheel is not. + try: + pkg_resources.get_distribution('wheel') + except pkg_resources.DistributionNotFound: + dist.announce('WARNING: The wheel package is not available.', log.WARN) + if not isinstance(req, pkg_resources.Requirement): + req = pkg_resources.Requirement.parse(req) + # Take easy_install options into account, but do not override relevant + # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll + # take precedence. + opts = dist.get_option_dict('easy_install') + if 'allow_hosts' in opts: + raise DistutilsError('the `allow-hosts` option is not supported ' + 'when using pip to install requirements.') + if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ: + quiet = False + else: + quiet = True + if 'PIP_INDEX_URL' in os.environ: + index_url = None + elif 'index_url' in opts: + index_url = opts['index_url'][1] + else: + index_url = None + if 'find_links' in opts: + find_links = opts['find_links'][1][:] + else: + find_links = [] + if dist.dependency_links: + find_links.extend(dist.dependency_links) + eggs_dir = os.path.realpath(dist.get_egg_cache_dir()) + environment = pkg_resources.Environment() + for egg_dist in pkg_resources.find_distributions(eggs_dir): + if egg_dist in req and environment.can_add(egg_dist): + return egg_dist + with TemporaryDirectory() as tmpdir: + cmd = [ + sys.executable, '-m', 'pip', + '--disable-pip-version-check', + 'wheel', '--no-deps', + '-w', tmpdir, + ] + if quiet: + cmd.append('--quiet') + if index_url is not None: + cmd.extend(('--index-url', index_url)) + if find_links is not None: + for link in find_links: + cmd.extend(('--find-links', link)) + # If requirement is a PEP 508 direct URL, directly pass + # the URL to pip, as `req @ url` does not work on the + # command line. + if req.url: + cmd.append(req.url) + else: + cmd.append(str(req)) + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + raise DistutilsError(str(e)) + wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0]) + dist_location = os.path.join(eggs_dir, wheel.egg_name()) + wheel.install_as_egg(dist_location) + dist_metadata = pkg_resources.PathMetadata( + dist_location, os.path.join(dist_location, 'EGG-INFO')) + dist = pkg_resources.Distribution.from_filename( + dist_location, metadata=dist_metadata) + return dist diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py index fc3a5975..8b17b081 100644 --- a/setuptools/tests/server.py +++ b/setuptools/tests/server.py @@ -1,10 +1,13 @@ """Basic http server for tests to simulate PyPI or custom indexes """ +import os import time import threading from setuptools.extern.six.moves import BaseHTTPServer, SimpleHTTPServer +from setuptools.extern.six.moves.urllib_parse import urljoin +from setuptools.extern.six.moves.urllib.request import pathname2url class IndexServer(BaseHTTPServer.HTTPServer): @@ -69,6 +72,20 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread): def run(self): self.serve_forever() + @property + def netloc(self): + return 'localhost:%s' % self.server_port + @property def url(self): - return 'http://localhost:%(server_port)s/' % vars(self) + return 'http://%s/' % self.netloc + + +def path_to_url(path, authority=None): + """ Convert a path to a file: URL. """ + path = os.path.normpath(os.path.abspath(path)) + base = 'file:' + if authority is not None: + base += '//' + authority + url = urljoin(base, pathname2url(path)) + return url diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index c3fd1c6e..aa75899a 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -15,24 +15,24 @@ import distutils.errors import io import zipfile import mock -from setuptools.command.easy_install import ( - EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter, -) import time + from setuptools.extern import six -from setuptools.extern.six.moves import urllib import pytest from setuptools import sandbox from setuptools.sandbox import run_setup import setuptools.command.easy_install as ei -from setuptools.command.easy_install import PthDistributions +from setuptools.command.easy_install import ( + EasyInstallDeprecationWarning, ScriptWriter, PthDistributions, + WindowsScriptWriter, +) from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution from pkg_resources import normalize_path, working_set from pkg_resources import Distribution as PRDistribution -import setuptools.tests.server +from setuptools.tests.server import MockServer, path_to_url from setuptools.tests import fail_on_ascii import pkg_resources @@ -440,35 +440,40 @@ def distutils_package(): yield +@pytest.fixture +def mock_index(): + # set up a server which will simulate an alternate package index. + p_index = MockServer() + if p_index.server_port == 0: + # Some platforms (Jython) don't find a port to which to bind, + # so skip test for them. + pytest.skip("could not find a valid port") + p_index.start() + return p_index + + class TestDistutilsPackage: def test_bdist_egg_available_on_distutils_pkg(self, distutils_package): run_setup('setup.py', ['bdist_egg']) class TestSetupRequires: - def test_setup_requires_honors_fetch_params(self): + + def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch): """ When easy_install installs a source distribution which specifies setup_requires, it should honor the fetch parameters (such as - allow-hosts, index-url, and find-links). + index-url, and find-links). """ - # set up a server which will simulate an alternate package index. - p_index = setuptools.tests.server.MockServer() - p_index.start() - netloc = 1 - p_index_loc = urllib.parse.urlparse(p_index.url)[netloc] - if p_index_loc.endswith(':0'): - # Some platforms (Jython) don't find a port to which to bind, - # so skip this test for them. - return + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) with contexts.quiet(): # create an sdist that has a build-time dependency. with TestSetupRequires.create_sdist() as dist_file: with contexts.tempdir() as temp_install_dir: with contexts.environment(PYTHONPATH=temp_install_dir): ei_params = [ - '--index-url', p_index.url, - '--allow-hosts', p_index_loc, + '--index-url', mock_index.url, '--exclude-scripts', '--install-dir', temp_install_dir, dist_file, @@ -478,10 +483,8 @@ class TestSetupRequires: # fail because it doesn't exist. with pytest.raises(SystemExit): easy_install_pkg.main(ei_params) - # there should have been two or three requests to the server - # (three happens on Python 3.3a) - assert 2 <= len(p_index.requests) <= 3 - assert p_index.requests[0].path == '/does-not-exist/' + # there should have been one requests to the server + assert [r.path for r in mock_index.requests] == ['/does-not-exist/'] @staticmethod @contextlib.contextmanager @@ -500,7 +503,9 @@ class TestSetupRequires: version="1.0", setup_requires = ['does-not-exist'], ) - """))]) + """)), + ('setup.cfg', ''), + ]) yield dist_path use_setup_cfg = ( @@ -632,6 +637,113 @@ class TestSetupRequires: assert len(lines) > 0 assert lines[-1].strip() == '42' + def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch): + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + test_pkg = create_setup_requires_package( + temp_dir, 'python-xlib', '0.19', + setup_attrs=dict(dependency_links=[])) + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + with open(test_setup_cfg, 'w') as fp: + fp.write(DALS( + ''' + [easy_install] + index_url = https://pypi.org/legacy/ + ''')) + test_setup_py = os.path.join(test_pkg, 'setup.py') + with pytest.raises(distutils.errors.DistutilsError): + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 1 + assert mock_index.requests[0].path == '/python-xlib/' + + def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch): + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url) + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + dep_sdist = os.path.join(temp_dir, 'dep.tar.gz') + make_trivial_sdist(dep_sdist, 'dependency', '42') + dep_url = path_to_url(dep_sdist, authority='localhost') + test_pkg = create_setup_requires_package( + temp_dir, + 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). + setup_attrs=dict(setup_requires='dependency @ %s' % dep_url)) + test_setup_py = os.path.join(test_pkg, 'setup.py') + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 0 + + def test_setup_requires_with_allow_hosts(self, mock_index): + ''' The `allow-hosts` option in not supported anymore. ''' + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + test_pkg = os.path.join(temp_dir, 'test_pkg') + test_setup_py = os.path.join(test_pkg, 'setup.py') + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + os.mkdir(test_pkg) + with open(test_setup_py, 'w') as fp: + fp.write(DALS( + ''' + from setuptools import setup + setup(setup_requires='python-xlib') + ''')) + with open(test_setup_cfg, 'w') as fp: + fp.write(DALS( + ''' + [easy_install] + allow_hosts = * + ''')) + with pytest.raises(distutils.errors.DistutilsError): + run_setup(test_setup_py, [str('--version')]) + assert len(mock_index.requests) == 0 + + def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir): + ''' Check `python_requires` is honored. ''' + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) + monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) + dep_1_0_sdist = 'dep-1.0.tar.gz' + dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist)) + dep_1_0_python_requires = '>=2.7' + make_python_requires_sdist(str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) + dep_2_0_sdist = 'dep-2.0.tar.gz' + dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist)) + dep_2_0_python_requires = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*' + make_python_requires_sdist(str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) + index = tmpdir / 'index.html' + index.write_text(DALS( + ''' + + Links for dep + +

Links for dep

+ {dep_1_0_sdist}
+ {dep_2_0_sdist}
+ + + ''').format( + dep_1_0_url=dep_1_0_url, + dep_1_0_sdist=dep_1_0_sdist, + dep_1_0_python_requires=dep_1_0_python_requires, + dep_2_0_url=dep_2_0_url, + dep_2_0_sdist=dep_2_0_sdist, + dep_2_0_python_requires=dep_2_0_python_requires, + ), 'utf-8') + index_url = path_to_url(str(index)) + with contexts.save_pkg_resources_state(): + test_pkg = create_setup_requires_package( + str(tmpdir), + 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). + setup_attrs=dict(setup_requires='dep', dependency_links=[index_url])) + test_setup_py = os.path.join(test_pkg, 'setup.py') + run_setup(test_setup_py, [str('--version')]) + eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) + assert eggs == ['dep 1.0'] + def make_trivial_sdist(dist_path, distname, version): """ @@ -647,7 +759,9 @@ def make_trivial_sdist(dist_path, distname, version): name=%r, version=%r ) - """ % (distname, version)))]) + """ % (distname, version))), + ('setup.cfg', ''), + ]) def make_nspkg_sdist(dist_path, distname, version): @@ -683,12 +797,29 @@ def make_nspkg_sdist(dist_path, distname, version): make_sdist(dist_path, files) +def make_python_requires_sdist(dist_path, distname, version, python_requires): + make_sdist(dist_path, [ + ('setup.py', DALS("""\ + import setuptools + setuptools.setup( + name={name!r}, + version={version!r}, + python_requires={python_requires!r}, + ) + """).format(name=distname, version=version, + python_requires=python_requires)), + ('setup.cfg', ''), + ]) + + def make_sdist(dist_path, files): """ Create a simple sdist tarball at dist_path, containing the files listed in ``files`` as ``(filename, content)`` tuples. """ + # Distributions with only one file don't play well with pip. + assert len(files) > 1 with tarfile.open(dist_path, 'w:gz') as dist: for filename, content in files: file_bytes = io.BytesIO(content.encode('utf-8')) @@ -721,8 +852,8 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', test_pkg = os.path.join(path, 'test_pkg') os.mkdir(test_pkg) + # setup.cfg if use_setup_cfg: - test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') options = [] metadata = [] for name in use_setup_cfg: @@ -734,8 +865,7 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', if isinstance(value, (tuple, list)): value = ';'.join(value) section.append('%s: %s' % (name, value)) - with open(test_setup_cfg, 'w') as f: - f.write(DALS( + test_setup_cfg_contents = DALS( """ [metadata] {metadata} @@ -745,16 +875,19 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', ).format( options='\n'.join(options), metadata='\n'.join(metadata), - )) - - test_setup_py = os.path.join(test_pkg, 'setup.py') + ) + else: + test_setup_cfg_contents = '' + with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f: + f.write(test_setup_cfg_contents) + # setup.py if setup_py_template is None: setup_py_template = DALS("""\ import setuptools setuptools.setup(**%r) """) - with open(test_setup_py, 'w') as f: + with open(os.path.join(test_pkg, 'setup.py'), 'w') as f: f.write(setup_py_template % test_setup_attrs) foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version)) diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 74a1284c..cd3d9313 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -121,14 +121,12 @@ def test_pip_upgrade_from_source(pip_version, virtualenv): virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist) -def test_test_command_install_requirements(bare_virtualenv, tmpdir): +def _check_test_command_install_requirements(virtualenv, tmpdir): """ Check the test command will install all required dependencies. """ - bare_virtualenv.run(' && '.join(( - 'cd {source}', - 'python setup.py develop', - )).format(source=SOURCE_DIR)) + # Install setuptools. + virtualenv.run('python setup.py develop', cd=SOURCE_DIR) def sdist(distname, version): dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version)) @@ -179,12 +177,20 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir): open('success', 'w').close() ''')) # Run test command for test package. - bare_virtualenv.run(' && '.join(( + virtualenv.run(' && '.join(( 'cd {tmpdir}', 'python setup.py test -s test', )).format(tmpdir=tmpdir)) assert tmpdir.join('success').check() +def test_test_command_install_requirements(virtualenv, tmpdir): + # Ensure pip/wheel packages are installed. + virtualenv.run("python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") + _check_test_command_install_requirements(virtualenv, tmpdir) + +def test_test_command_install_requirements_when_using_easy_install(bare_virtualenv, tmpdir): + _check_test_command_install_requirements(bare_virtualenv, tmpdir) + def test_no_missing_dependencies(bare_virtualenv): """ diff --git a/tests/requirements.txt b/tests/requirements.txt index 1f70adee..1f8bd19d 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -9,3 +9,4 @@ 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. -- cgit v1.2.3 From 6e1838a9fb5feb000ba9b6a3c37c8b39d7e872b3 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Thu, 14 Nov 2019 21:51:33 +0100 Subject: drop easy_install script and associated documentation --- docs/easy_install.txt | 1085 --------------------------------- docs/index.txt | 1 - easy_install.py | 5 - setup.cfg | 1 - setup.py | 19 - setuptools/command/easy_install.py | 55 +- setuptools/tests/test_easy_install.py | 34 +- setuptools/tests/test_namespaces.py | 5 +- 8 files changed, 21 insertions(+), 1184 deletions(-) delete mode 100644 docs/easy_install.txt delete mode 100644 easy_install.py diff --git a/docs/easy_install.txt b/docs/easy_install.txt deleted file mode 100644 index 544b9efd..00000000 --- a/docs/easy_install.txt +++ /dev/null @@ -1,1085 +0,0 @@ -============ -Easy Install -============ - -.. warning:: - Easy Install is deprecated. Do not use it. Instead use pip. If - you think you need Easy Install, please reach out to the PyPA - team (a ticket to pip or setuptools is fine), describing your - use-case. - -Easy Install is a python module (``easy_install``) bundled with ``setuptools`` -that lets you automatically download, build, install, and manage Python -packages. - -Please share your experiences with us! If you encounter difficulty installing -a package, please contact us via the `distutils mailing list -`_. (Note: please DO NOT send -private email directly to the author of setuptools; it will be discarded. The -mailing list is a searchable archive of previously-asked and answered -questions; you should begin your research there before reporting something as a -bug -- and then do so via list discussion first.) - -(Also, if you'd like to learn about how you can use ``setuptools`` to make your -own packages work better with EasyInstall, or provide EasyInstall-like features -without requiring your users to use EasyInstall directly, you'll probably want -to check out the full documentation as well.) - -.. contents:: **Table of Contents** - - -Using "Easy Install" -==================== - - -.. _installation instructions: - -Installing "Easy Install" -------------------------- - -Please see the `setuptools PyPI page `_ -for download links and basic installation instructions for each of the -supported platforms. - -You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be -installed in the normal location for Python scripts on your platform. - -Note that the instructions on the setuptools PyPI page assume that you are -are installing to Python's primary ``site-packages`` directory. If this is -not the case, you should consult the section below on `Custom Installation -Locations`_ before installing. (And, on Windows, you should not use the -``.exe`` installer when installing to an alternate location.) - -Note that ``easy_install`` normally works by downloading files from the -internet. If you are behind an NTLM-based firewall that prevents Python -programs from accessing the net directly, you may wish to first install and use -the `APS proxy server `_, which lets you get past such -firewalls in the same way that your web browser(s) do. - -(Alternately, if you do not wish easy_install to actually download anything, you -can restrict it from doing so with the ``--allow-hosts`` option; see the -sections on `restricting downloads with --allow-hosts`_ and `command-line -options`_ for more details.) - - -Troubleshooting -~~~~~~~~~~~~~~~ - -If EasyInstall/setuptools appears to install correctly, and you can run the -``easy_install`` command but it fails with an ``ImportError``, the most likely -cause is that you installed to a location other than ``site-packages``, -without taking any of the steps described in the `Custom Installation -Locations`_ section below. Please see that section and follow the steps to -make sure that your custom location will work correctly. Then re-install. - -Similarly, if you can run ``easy_install``, and it appears to be installing -packages, but then you can't import them, the most likely issue is that you -installed EasyInstall correctly but are using it to install packages to a -non-standard location that hasn't been properly prepared. Again, see the -section on `Custom Installation Locations`_ for more details. - - -Windows Notes -~~~~~~~~~~~~~ - -Installing setuptools will provide an ``easy_install`` command according to -the techniques described in `Executables and Launchers`_. If the -``easy_install`` command is not available after installation, that section -provides details on how to configure Windows to make the commands available. - - -Downloading and Installing a Package ------------------------------------- - -For basic use of ``easy_install``, you need only supply the filename or URL of -a source distribution or .egg file (`Python Egg`__). - -__ http://peak.telecommunity.com/DevCenter/PythonEggs - -**Example 1**. Install a package by name, searching PyPI for the latest -version, and automatically downloading, building, and installing it:: - - easy_install SQLObject - -**Example 2**. Install or upgrade a package by name and version by finding -links on a given "download page":: - - easy_install -f http://pythonpaste.org/package_index.html SQLObject - -**Example 3**. Download a source distribution from a specified URL, -automatically building and installing it:: - - easy_install http://example.com/path/to/MyPackage-1.2.3.tgz - -**Example 4**. Install an already-downloaded .egg file:: - - easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg - -**Example 5**. Upgrade an already-installed package to the latest version -listed on PyPI:: - - easy_install --upgrade PyProtocols - -**Example 6**. Install a source distribution that's already downloaded and -extracted in the current directory (New in 0.5a9):: - - easy_install . - -**Example 7**. (New in 0.6a1) Find a source distribution or Subversion -checkout URL for a package, and extract it or check it out to -``~/projects/sqlobject`` (the name will always be in all-lowercase), where it -can be examined or edited. (The package will not be installed, but it can -easily be installed with ``easy_install ~/projects/sqlobject``. See `Editing -and Viewing Source Packages`_ below for more info.):: - - easy_install --editable --build-directory ~/projects SQLObject - -**Example 7**. (New in 0.6.11) Install a distribution within your home dir:: - - easy_install --user SQLAlchemy - -Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils`` -"distribution" names), and package+version specifiers. In each case, it will -attempt to locate the latest available version that meets your criteria. - -When downloading or processing downloaded files, Easy Install recognizes -distutils source distribution files with extensions of .tgz, .tar, .tar.gz, -.tar.bz2, or .zip. And of course it handles already-built .egg -distributions as well as ``.win32.exe`` installers built using distutils. - -By default, packages are installed to the running Python installation's -``site-packages`` directory, unless you provide the ``-d`` or ``--install-dir`` -option to specify an alternative directory, or specify an alternate location -using distutils configuration files. (See `Configuration Files`_, below.) - -By default, any scripts included with the package are installed to the running -Python installation's standard script installation location. However, if you -specify an installation directory via the command line or a config file, then -the default directory for installing scripts will be the same as the package -installation directory, to ensure that the script will have access to the -installed package. You can override this using the ``-s`` or ``--script-dir`` -option. - -Installed packages are added to an ``easy-install.pth`` file in the install -directory, so that Python will always use the most-recently-installed version -of the package. If you would like to be able to select which version to use at -runtime, you should use the ``-m`` or ``--multi-version`` option. - - -Upgrading a Package -------------------- - -You don't need to do anything special to upgrade a package: just install the -new version, either by requesting a specific version, e.g.:: - - easy_install "SomePackage==2.0" - -a version greater than the one you have now:: - - easy_install "SomePackage>2.0" - -using the upgrade flag, to find the latest available version on PyPI:: - - easy_install --upgrade SomePackage - -or by using a download page, direct download URL, or package filename:: - - easy_install -f http://example.com/downloads ExamplePackage - - easy_install http://example.com/downloads/ExamplePackage-2.0-py2.4.egg - - easy_install my_downloads/ExamplePackage-2.0.tgz - -If you're using ``-m`` or ``--multi-version`` , using the ``require()`` -function at runtime automatically selects the newest installed version of a -package that meets your version criteria. So, installing a newer version is -the only step needed to upgrade such packages. - -If you're installing to a directory on PYTHONPATH, or a configured "site" -directory (and not using ``-m``), installing a package automatically replaces -any previous version in the ``easy-install.pth`` file, so that Python will -import the most-recently installed version by default. So, again, installing -the newer version is the only upgrade step needed. - -If you haven't suppressed script installation (using ``--exclude-scripts`` or -``-x``), then the upgraded version's scripts will be installed, and they will -be automatically patched to ``require()`` the corresponding version of the -package, so that you can use them even if they are installed in multi-version -mode. - -``easy_install`` never actually deletes packages (unless you're installing a -package with the same name and version number as an existing package), so if -you want to get rid of older versions of a package, please see `Uninstalling -Packages`_, below. - - -Changing the Active Version ---------------------------- - -If you've upgraded a package, but need to revert to a previously-installed -version, you can do so like this:: - - easy_install PackageName==1.2.3 - -Where ``1.2.3`` is replaced by the exact version number you wish to switch to. -If a package matching the requested name and version is not already installed -in a directory on ``sys.path``, it will be located via PyPI and installed. - -If you'd like to switch to the latest installed version of ``PackageName``, you -can do so like this:: - - easy_install PackageName - -This will activate the latest installed version. (Note: if you have set any -``find_links`` via distutils configuration files, those download pages will be -checked for the latest available version of the package, and it will be -downloaded and installed if it is newer than your current version.) - -Note that changing the active version of a package will install the newly -active version's scripts, unless the ``--exclude-scripts`` or ``-x`` option is -specified. - - -Uninstalling Packages ---------------------- - -If you have replaced a package with another version, then you can just delete -the package(s) you don't need by deleting the PackageName-versioninfo.egg file -or directory (found in the installation directory). - -If you want to delete the currently installed version of a package (or all -versions of a package), you should first run:: - - easy_install -m PackageName - -This will ensure that Python doesn't continue to search for a package you're -planning to remove. After you've done this, you can safely delete the .egg -files or directories, along with any scripts you wish to remove. - - -Managing Scripts ----------------- - -Whenever you install, upgrade, or change versions of a package, EasyInstall -automatically installs the scripts for the selected package version, unless -you tell it not to with ``-x`` or ``--exclude-scripts``. If any scripts in -the script directory have the same name, they are overwritten. - -Thus, you do not normally need to manually delete scripts for older versions of -a package, unless the newer version of the package does not include a script -of the same name. However, if you are completely uninstalling a package, you -may wish to manually delete its scripts. - -EasyInstall's default behavior means that you can normally only run scripts -from one version of a package at a time. If you want to keep multiple versions -of a script available, however, you can simply use the ``--multi-version`` or -``-m`` option, and rename the scripts that EasyInstall creates. This works -because EasyInstall installs scripts as short code stubs that ``require()`` the -matching version of the package the script came from, so renaming the script -has no effect on what it executes. - -For example, suppose you want to use two versions of the ``rst2html`` tool -provided by the `docutils `_ package. You might -first install one version:: - - easy_install -m docutils==0.3.9 - -then rename the ``rst2html.py`` to ``r2h_039``, and install another version:: - - easy_install -m docutils==0.3.10 - -This will create another ``rst2html.py`` script, this one using docutils -version 0.3.10 instead of 0.3.9. You now have two scripts, each using a -different version of the package. (Notice that we used ``-m`` for both -installations, so that Python won't lock us out of using anything but the most -recently-installed version of the package.) - - -Executables and Launchers -------------------------- - -On Unix systems, scripts are installed with as natural files with a "#!" -header and no extension and they launch under the Python version indicated in -the header. - -On Windows, there is no mechanism to "execute" files without extensions, so -EasyInstall provides two techniques to mirror the Unix behavior. The behavior -is indicated by the SETUPTOOLS_LAUNCHER environment variable, which may be -"executable" (default) or "natural". - -Regardless of the technique used, the script(s) will be installed to a Scripts -directory (by default in the Python installation directory). It is recommended -for EasyInstall that you ensure this directory is in the PATH environment -variable. The easiest way to ensure the Scripts directory is in the PATH is -to run ``Tools\Scripts\win_add2path.py`` from the Python directory. - -Note that instead of changing your ``PATH`` to include the Python scripts -directory, you can also retarget the installation location for scripts so they -go on a directory that's already on the ``PATH``. For more information see -`Command-Line Options`_ and `Configuration Files`_. During installation, -pass command line options (such as ``--script-dir``) to control where -scripts will be installed. - - -Windows Executable Launcher -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the "executable" launcher is used, EasyInstall will create a '.exe' -launcher of the same name beside each installed script (including -``easy_install`` itself). These small .exe files launch the script of the -same name using the Python version indicated in the '#!' header. - -This behavior is currently default. To force -the use of executable launchers, set ``SETUPTOOLS_LAUNCHER`` to "executable". - -Natural Script Launcher -~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall also supports deferring to an external launcher such as -`pylauncher `_ for launching scripts. -Enable this experimental functionality by setting the -``SETUPTOOLS_LAUNCHER`` environment variable to "natural". EasyInstall will -then install scripts as simple -scripts with a .pya (or .pyw) extension appended. If these extensions are -associated with the pylauncher and listed in the PATHEXT environment variable, -these scripts can then be invoked simply and directly just like any other -executable. This behavior may become default in a future version. - -EasyInstall uses the .pya extension instead of simply -the typical '.py' extension. This distinct extension is necessary to prevent -Python -from treating the scripts as importable modules (where name conflicts exist). -Current releases of pylauncher do not yet associate with .pya files by -default, but future versions should do so. - - -Tips & Techniques ------------------ - -Multiple Python Versions -~~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall installs itself under two names: -``easy_install`` and ``easy_install-N.N``, where ``N.N`` is the Python version -used to install it. Thus, if you install EasyInstall for both Python 3.2 and -2.7, you can use the ``easy_install-3.2`` or ``easy_install-2.7`` scripts to -install packages for the respective Python version. - -Setuptools also supplies easy_install as a runnable module which may be -invoked using ``python -m easy_install`` for any Python with Setuptools -installed. - -Restricting Downloads with ``--allow-hosts`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can use the ``--allow-hosts`` (``-H``) option to restrict what domains -EasyInstall will look for links and downloads on. ``--allow-hosts=None`` -prevents downloading altogether. You can also use wildcards, for example -to restrict downloading to hosts in your own intranet. See the section below -on `Command-Line Options`_ for more details on the ``--allow-hosts`` option. - -By default, there are no host restrictions in effect, but you can change this -default by editing the appropriate `configuration files`_ and adding: - -.. code-block:: ini - - [easy_install] - allow_hosts = *.myintranet.example.com,*.python.org - -The above example would then allow downloads only from hosts in the -``python.org`` and ``myintranet.example.com`` domains, unless overridden on the -command line. - - -Installing on Un-networked Machines -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Just copy the eggs or source packages you need to a directory on the target -machine, then use the ``-f`` or ``--find-links`` option to specify that -directory's location. For example:: - - easy_install -H None -f somedir SomePackage - -will attempt to install SomePackage using only eggs and source packages found -in ``somedir`` and disallowing all remote access. You should of course make -sure you have all of SomePackage's dependencies available in somedir. - -If you have another machine of the same operating system and library versions -(or if the packages aren't platform-specific), you can create the directory of -eggs using a command like this:: - - easy_install -zmaxd somedir SomePackage - -This will tell EasyInstall to put zipped eggs or source packages for -SomePackage and all its dependencies into ``somedir``, without creating any -scripts or .pth files. You can then copy the contents of ``somedir`` to the -target machine. (``-z`` means zipped eggs, ``-m`` means multi-version, which -prevents .pth files from being used, ``-a`` means to copy all the eggs needed, -even if they're installed elsewhere on the machine, and ``-d`` indicates the -directory to place the eggs in.) - -You can also build the eggs from local development packages that were installed -with the ``setup.py develop`` command, by including the ``-l`` option, e.g.:: - - easy_install -zmaxld somedir SomePackage - -This will use locally-available source distributions to build the eggs. - - -Packaging Others' Projects As Eggs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Need to distribute a package that isn't published in egg form? You can use -EasyInstall to build eggs for a project. You'll want to use the ``--zip-ok``, -``--exclude-scripts``, and possibly ``--no-deps`` options (``-z``, ``-x`` and -``-N``, respectively). Use ``-d`` or ``--install-dir`` to specify the location -where you'd like the eggs placed. By placing them in a directory that is -published to the web, you can then make the eggs available for download, either -in an intranet or to the internet at large. - -If someone distributes a package in the form of a single ``.py`` file, you can -wrap it in an egg by tacking an ``#egg=name-version`` suffix on the file's URL. -So, something like this:: - - easy_install -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo - -will install the package as an egg, and this:: - - easy_install -zmaxd. \ - -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo - -will create a ``.egg`` file in the current directory. - - -Creating your own Package Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In addition to local directories and the Python Package Index, EasyInstall can -find download links on most any web page whose URL is given to the ``-f`` -(``--find-links``) option. In the simplest case, you can simply have a web -page with links to eggs or Python source packages, even an automatically -generated directory listing (such as the Apache web server provides). - -If you are setting up an intranet site for package downloads, you may want to -configure the target machines to use your download site by default, adding -something like this to their `configuration files`_: - -.. code-block:: ini - - [easy_install] - find_links = http://mypackages.example.com/somedir/ - http://turbogears.org/download/ - http://peak.telecommunity.com/dist/ - -As you can see, you can list multiple URLs separated by whitespace, continuing -on multiple lines if necessary (as long as the subsequent lines are indented. - -If you are more ambitious, you can also create an entirely custom package index -or PyPI mirror. See the ``--index-url`` option under `Command-Line Options`_, -below, and also the section on `Package Index "API"`_. - - -Password-Protected Sites ------------------------- - -If a site you want to download from is password-protected using HTTP "Basic" -authentication, you can specify your credentials in the URL, like so:: - - http://some_userid:some_password@some.example.com/some_path/ - -You can do this with both index page URLs and direct download URLs. As long -as any HTML pages read by easy_install use *relative* links to point to the -downloads, the same user ID and password will be used to do the downloading. - -Using .pypirc Credentials -------------------------- - -In additional to supplying credentials in the URL, ``easy_install`` will also -honor credentials if present in the .pypirc file. Teams maintaining a private -repository of packages may already have defined access credentials for -uploading packages according to the distutils documentation. ``easy_install`` -will attempt to honor those if present. Refer to the distutils documentation -for Python 2.5 or later for details on the syntax. - -Controlling Build Options -~~~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall respects standard distutils `Configuration Files`_, so you can use -them to configure build options for packages that it installs from source. For -example, if you are on Windows using the MinGW compiler, you can configure the -default compiler by putting something like this: - -.. code-block:: ini - - [build] - compiler = mingw32 - -into the appropriate distutils configuration file. In fact, since this is just -normal distutils configuration, it will affect any builds using that config -file, not just ones done by EasyInstall. For example, if you add those lines -to ``distutils.cfg`` in the ``distutils`` package directory, it will be the -default compiler for *all* packages you build. See `Configuration Files`_ -below for a list of the standard configuration file locations, and links to -more documentation on using distutils configuration files. - - -Editing and Viewing Source Packages -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes a package's source distribution contains additional documentation, -examples, configuration files, etc., that are not part of its actual code. If -you want to be able to examine these files, you can use the ``--editable`` -option to EasyInstall, and EasyInstall will look for a source distribution -or Subversion URL for the package, then download and extract it or check it out -as a subdirectory of the ``--build-directory`` you specify. If you then wish -to install the package after editing or configuring it, you can do so by -rerunning EasyInstall with that directory as the target. - -Note that using ``--editable`` stops EasyInstall from actually building or -installing the package; it just finds, obtains, and possibly unpacks it for -you. This allows you to make changes to the package if necessary, and to -either install it in development mode using ``setup.py develop`` (if the -package uses setuptools, that is), or by running ``easy_install projectdir`` -(where ``projectdir`` is the subdirectory EasyInstall created for the -downloaded package. - -In order to use ``--editable`` (``-e`` for short), you *must* also supply a -``--build-directory`` (``-b`` for short). The project will be placed in a -subdirectory of the build directory. The subdirectory will have the same -name as the project itself, but in all-lowercase. If a file or directory of -that name already exists, EasyInstall will print an error message and exit. - -Also, when using ``--editable``, you cannot use URLs or filenames as arguments. -You *must* specify project names (and optional version requirements) so that -EasyInstall knows what directory name(s) to create. If you need to force -EasyInstall to use a particular URL or filename, you should specify it as a -``--find-links`` item (``-f`` for short), and then also specify -the project name, e.g.:: - - easy_install -eb ~/projects \ - -fhttp://prdownloads.sourceforge.net/ctypes/ctypes-0.9.6.tar.gz?download \ - ctypes==0.9.6 - - -Dealing with Installation Conflicts -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(NOTE: As of 0.6a11, this section is obsolete; it is retained here only so that -people using older versions of EasyInstall can consult it. As of version -0.6a11, installation conflicts are handled automatically without deleting the -old or system-installed packages, and without ignoring the issue. Instead, -eggs are automatically shifted to the front of ``sys.path`` using special -code added to the ``easy-install.pth`` file. So, if you are using version -0.6a11 or better of setuptools, you do not need to worry about conflicts, -and the following issues do not apply to you.) - -EasyInstall installs distributions in a "managed" way, such that each -distribution can be independently activated or deactivated on ``sys.path``. -However, packages that were not installed by EasyInstall are "unmanaged", -in that they usually live all in one directory and cannot be independently -activated or deactivated. - -As a result, if you are using EasyInstall to upgrade an existing package, or -to install a package with the same name as an existing package, EasyInstall -will warn you of the conflict. (This is an improvement over ``setup.py -install``, because the ``distutils`` just install new packages on top of old -ones, possibly combining two unrelated packages or leaving behind modules that -have been deleted in the newer version of the package.) - -EasyInstall will stop the installation if it detects a conflict -between an existing, "unmanaged" package, and a module or package in any of -the distributions you're installing. It will display a list of all of the -existing files and directories that would need to be deleted for the new -package to be able to function correctly. To proceed, you must manually -delete these conflicting files and directories and re-run EasyInstall. - -Of course, once you've replaced all of your existing "unmanaged" packages with -versions managed by EasyInstall, you won't have any more conflicts to worry -about! - - -Compressed Installation -~~~~~~~~~~~~~~~~~~~~~~~ - -EasyInstall tries to install packages in zipped form, if it can. Zipping -packages can improve Python's overall import performance if you're not using -the ``--multi-version`` option, because Python processes zipfile entries on -``sys.path`` much faster than it does directories. - -As of version 0.5a9, EasyInstall analyzes packages to determine whether they -can be safely installed as a zipfile, and then acts on its analysis. (Previous -versions would not install a package as a zipfile unless you used the -``--zip-ok`` option.) - -The current analysis approach is fairly conservative; it currently looks for: - - * Any use of the ``__file__`` or ``__path__`` variables (which should be - replaced with ``pkg_resources`` API calls) - - * Possible use of ``inspect`` functions that expect to manipulate source files - (e.g. ``inspect.getsource()``) - - * Top-level modules that might be scripts used with ``python -m`` (Python 2.4) - -If any of the above are found in the package being installed, EasyInstall will -assume that the package cannot be safely run from a zipfile, and unzip it to -a directory instead. You can override this analysis with the ``-zip-ok`` flag, -which will tell EasyInstall to install the package as a zipfile anyway. Or, -you can use the ``--always-unzip`` flag, in which case EasyInstall will always -unzip, even if its analysis says the package is safe to run as a zipfile. - -Normally, however, it is simplest to let EasyInstall handle the determination -of whether to zip or unzip, and only specify overrides when needed to work -around a problem. If you find you need to override EasyInstall's guesses, you -may want to contact the package author and the EasyInstall maintainers, so that -they can make appropriate changes in future versions. - -(Note: If a package uses ``setuptools`` in its setup script, the package author -has the option to declare the package safe or unsafe for zipped usage via the -``zip_safe`` argument to ``setup()``. If the package author makes such a -declaration, EasyInstall believes the package's author and does not perform its -own analysis. However, your command-line option, if any, will still override -the package author's choice.) - - -Reference Manual -================ - -Configuration Files -------------------- - -(New in 0.4a2) - -You may specify default options for EasyInstall using the standard -distutils configuration files, under the command heading ``easy_install``. -EasyInstall will look first for a ``setup.cfg`` file in the current directory, -then a ``~/.pydistutils.cfg`` or ``$HOME\\pydistutils.cfg`` (on Unix-like OSes -and Windows, respectively), and finally a ``distutils.cfg`` file in the -``distutils`` package directory. Here's a simple example: - -.. code-block:: ini - - [easy_install] - - # set the default location to install packages - install_dir = /home/me/lib/python - - # Notice that indentation can be used to continue an option - # value; this is especially useful for the "--find-links" - # option, which tells easy_install to use download links on - # these pages before consulting PyPI: - # - find_links = http://sqlobject.org/ - http://peak.telecommunity.com/dist/ - -In addition to accepting configuration for its own options under -``[easy_install]``, EasyInstall also respects defaults specified for other -distutils commands. For example, if you don't set an ``install_dir`` for -``[easy_install]``, but *have* set an ``install_lib`` for the ``[install]`` -command, this will become EasyInstall's default installation directory. Thus, -if you are already using distutils configuration files to set default install -locations, build options, etc., EasyInstall will respect your existing settings -until and unless you override them explicitly in an ``[easy_install]`` section. - -For more information, see also the current Python documentation on the `use and -location of distutils configuration files `_. - -Notice that ``easy_install`` will use the ``setup.cfg`` from the current -working directory only if it was triggered from ``setup.py`` through the -``install_requires`` option. The standalone command will not use that file. - -Command-Line Options --------------------- - -``--zip-ok, -z`` - Install all packages as zip files, even if they are marked as unsafe for - running as a zipfile. This can be useful when EasyInstall's analysis - of a non-setuptools package is too conservative, but keep in mind that - the package may not work correctly. (Changed in 0.5a9; previously this - option was required in order for zipped installation to happen at all.) - -``--always-unzip, -Z`` - Don't install any packages as zip files, even if the packages are marked - as safe for running as a zipfile. This can be useful if a package does - something unsafe, but not in a way that EasyInstall can easily detect. - EasyInstall's default analysis is currently very conservative, however, so - you should only use this option if you've had problems with a particular - package, and *after* reporting the problem to the package's maintainer and - to the EasyInstall maintainers. - - (Note: the ``-z/-Z`` options only affect the installation of newly-built - or downloaded packages that are not already installed in the target - directory; if you want to convert an existing installed version from - zipped to unzipped or vice versa, you'll need to delete the existing - version first, and re-run EasyInstall.) - -``--multi-version, -m`` - "Multi-version" mode. Specifying this option prevents ``easy_install`` from - adding an ``easy-install.pth`` entry for the package being installed, and - if an entry for any version the package already exists, it will be removed - upon successful installation. In multi-version mode, no specific version of - the package is available for importing, unless you use - ``pkg_resources.require()`` to put it on ``sys.path``. This can be as - simple as:: - - from pkg_resources import require - require("SomePackage", "OtherPackage", "MyPackage") - - which will put the latest installed version of the specified packages on - ``sys.path`` for you. (For more advanced uses, like selecting specific - versions and enabling optional dependencies, see the ``pkg_resources`` API - doc.) - - Changed in 0.6a10: this option is no longer silently enabled when - installing to a non-PYTHONPATH, non-"site" directory. You must always - explicitly use this option if you want it to be active. - -``--upgrade, -U`` (New in 0.5a4) - By default, EasyInstall only searches online if a project/version - requirement can't be met by distributions already installed - on sys.path or the installation directory. However, if you supply the - ``--upgrade`` or ``-U`` flag, EasyInstall will always check the package - index and ``--find-links`` URLs before selecting a version to install. In - this way, you can force EasyInstall to use the latest available version of - any package it installs (subject to any version requirements that might - exclude such later versions). - -``--install-dir=DIR, -d DIR`` - Set the installation directory. It is up to you to ensure that this - directory is on ``sys.path`` at runtime, and to use - ``pkg_resources.require()`` to enable the installed package(s) that you - need. - - (New in 0.4a2) If this option is not directly specified on the command line - or in a distutils configuration file, the distutils default installation - location is used. Normally, this would be the ``site-packages`` directory, - but if you are using distutils configuration files, setting things like - ``prefix`` or ``install_lib``, then those settings are taken into - account when computing the default installation directory, as is the - ``--prefix`` option. - -``--script-dir=DIR, -s DIR`` - Set the script installation directory. If you don't supply this option - (via the command line or a configuration file), but you *have* supplied - an ``--install-dir`` (via command line or config file), then this option - defaults to the same directory, so that the scripts will be able to find - their associated package installation. Otherwise, this setting defaults - to the location where the distutils would normally install scripts, taking - any distutils configuration file settings into account. - -``--exclude-scripts, -x`` - Don't install scripts. This is useful if you need to install multiple - versions of a package, but do not want to reset the version that will be - run by scripts that are already installed. - -``--user`` (New in 0.6.11) - Use the user-site-packages as specified in :pep:`370` - instead of the global site-packages. - -``--always-copy, -a`` (New in 0.5a4) - Copy all needed distributions to the installation directory, even if they - are already present in a directory on sys.path. In older versions of - EasyInstall, this was the default behavior, but now you must explicitly - request it. By default, EasyInstall will no longer copy such distributions - from other sys.path directories to the installation directory, unless you - explicitly gave the distribution's filename on the command line. - - Note that as of 0.6a10, using this option excludes "system" and - "development" eggs from consideration because they can't be reliably - copied. This may cause EasyInstall to choose an older version of a package - than what you expected, or it may cause downloading and installation of a - fresh copy of something that's already installed. You will see warning - messages for any eggs that EasyInstall skips, before it falls back to an - older version or attempts to download a fresh copy. - -``--find-links=URLS_OR_FILENAMES, -f URLS_OR_FILENAMES`` - Scan the specified "download pages" or directories for direct links to eggs - or other distributions. Any existing file or directory names or direct - download URLs are immediately added to EasyInstall's search cache, and any - indirect URLs (ones that don't point to eggs or other recognized archive - formats) are added to a list of additional places to search for download - links. As soon as EasyInstall has to go online to find a package (either - because it doesn't exist locally, or because ``--upgrade`` or ``-U`` was - used), the specified URLs will be downloaded and scanned for additional - direct links. - - Eggs and archives found by way of ``--find-links`` are only downloaded if - they are needed to meet a requirement specified on the command line; links - to unneeded packages are ignored. - - If all requested packages can be found using links on the specified - download pages, the Python Package Index will not be consulted unless you - also specified the ``--upgrade`` or ``-U`` option. - - (Note: if you want to refer to a local HTML file containing links, you must - use a ``file:`` URL, as filenames that do not refer to a directory, egg, or - archive are ignored.) - - You may specify multiple URLs or file/directory names with this option, - separated by whitespace. Note that on the command line, you will probably - have to surround the URL list with quotes, so that it is recognized as a - single option value. You can also specify URLs in a configuration file; - see `Configuration Files`_, above. - - Changed in 0.6a10: previously all URLs and directories passed to this - option were scanned as early as possible, but from 0.6a10 on, only - directories and direct archive links are scanned immediately; URLs are not - retrieved unless a package search was already going to go online due to a - package not being available locally, or due to the use of the ``--update`` - or ``-U`` option. - -``--no-find-links`` Blocks the addition of any link. - This parameter is useful if you want to avoid adding links defined in a - project easy_install is installing (whether it's a requested project or a - dependency). When used, ``--find-links`` is ignored. - - Added in Distribute 0.6.11 and Setuptools 0.7. - -``--index-url=URL, -i URL`` (New in 0.4a1; default changed in 0.6c7) - Specifies the base URL of the Python Package Index. The default is - https://pypi.org/simple/ if not specified. When a package is requested - that is not locally available or linked from a ``--find-links`` download - page, the package index will be searched for download pages for the needed - package, and those download pages will be searched for links to download - an egg or source distribution. - -``--editable, -e`` (New in 0.6a1) - Only find and download source distributions for the specified projects, - unpacking them to subdirectories of the specified ``--build-directory``. - EasyInstall will not actually build or install the requested projects or - their dependencies; it will just find and extract them for you. See - `Editing and Viewing Source Packages`_ above for more details. - -``--build-directory=DIR, -b DIR`` (UPDATED in 0.6a1) - Set the directory used to build source packages. If a package is built - from a source distribution or checkout, it will be extracted to a - subdirectory of the specified directory. The subdirectory will have the - same name as the extracted distribution's project, but in all-lowercase. - If a file or directory of that name already exists in the given directory, - a warning will be printed to the console, and the build will take place in - a temporary directory instead. - - This option is most useful in combination with the ``--editable`` option, - which forces EasyInstall to *only* find and extract (but not build and - install) source distributions. See `Editing and Viewing Source Packages`_, - above, for more information. - -``--verbose, -v, --quiet, -q`` (New in 0.4a4) - Control the level of detail of EasyInstall's progress messages. The - default detail level is "info", which prints information only about - relatively time-consuming operations like running a setup script, unpacking - an archive, or retrieving a URL. Using ``-q`` or ``--quiet`` drops the - detail level to "warn", which will only display installation reports, - warnings, and errors. Using ``-v`` or ``--verbose`` increases the detail - level to include individual file-level operations, link analysis messages, - and distutils messages from any setup scripts that get run. If you include - the ``-v`` option more than once, the second and subsequent uses are passed - down to any setup scripts, increasing the verbosity of their reporting as - well. - -``--dry-run, -n`` (New in 0.4a4) - Don't actually install the package or scripts. This option is passed down - to any setup scripts run, so packages should not actually build either. - This does *not* skip downloading, nor does it skip extracting source - distributions to a temporary/build directory. - -``--optimize=LEVEL``, ``-O LEVEL`` (New in 0.4a4) - If you are installing from a source distribution, and are *not* using the - ``--zip-ok`` option, this option controls the optimization level for - compiling installed ``.py`` files to ``.pyo`` files. It does not affect - the compilation of modules contained in ``.egg`` files, only those in - ``.egg`` directories. The optimization level can be set to 0, 1, or 2; - the default is 0 (unless it's set under ``install`` or ``install_lib`` in - one of your distutils configuration files). - -``--record=FILENAME`` (New in 0.5a4) - Write a record of all installed files to FILENAME. This is basically the - same as the same option for the standard distutils "install" command, and - is included for compatibility with tools that expect to pass this option - to "setup.py install". - -``--site-dirs=DIRLIST, -S DIRLIST`` (New in 0.6a1) - Specify one or more custom "site" directories (separated by commas). - "Site" directories are directories where ``.pth`` files are processed, such - as the main Python ``site-packages`` directory. As of 0.6a10, EasyInstall - automatically detects whether a given directory processes ``.pth`` files - (or can be made to do so), so you should not normally need to use this - option. It is is now only necessary if you want to override EasyInstall's - judgment and force an installation directory to be treated as if it - supported ``.pth`` files. - -``--no-deps, -N`` (New in 0.6a6) - Don't install any dependencies. This is intended as a convenience for - tools that wrap eggs in a platform-specific packaging system. (We don't - recommend that you use it for anything else.) - -``--allow-hosts=PATTERNS, -H PATTERNS`` (New in 0.6a6) - Restrict downloading and spidering to hosts matching the specified glob - patterns. E.g. ``-H *.python.org`` restricts web access so that only - packages listed and downloadable from machines in the ``python.org`` - domain. The glob patterns must match the *entire* user/host/port section of - the target URL(s). For example, ``*.python.org`` will NOT accept a URL - like ``http://python.org/foo`` or ``http://www.python.org:8080/``. - Multiple patterns can be specified by separating them with commas. The - default pattern is ``*``, which matches anything. - - In general, this option is mainly useful for blocking EasyInstall's web - access altogether (e.g. ``-Hlocalhost``), or to restrict it to an intranet - or other trusted site. EasyInstall will do the best it can to satisfy - dependencies given your host restrictions, but of course can fail if it - can't find suitable packages. EasyInstall displays all blocked URLs, so - that you can adjust your ``--allow-hosts`` setting if it is more strict - than you intended. Some sites may wish to define a restrictive default - setting for this option in their `configuration files`_, and then manually - override the setting on the command line as needed. - -``--prefix=DIR`` (New in 0.6a10) - Use the specified directory as a base for computing the default - installation and script directories. On Windows, the resulting default - directories will be ``prefix\\Lib\\site-packages`` and ``prefix\\Scripts``, - while on other platforms the defaults will be - ``prefix/lib/python2.X/site-packages`` (with the appropriate version - substituted) for libraries and ``prefix/bin`` for scripts. - - Note that the ``--prefix`` option only sets the *default* installation and - script directories, and does not override the ones set on the command line - or in a configuration file. - -``--local-snapshots-ok, -l`` (New in 0.6c6) - Normally, EasyInstall prefers to only install *released* versions of - projects, not in-development ones, because such projects may not - have a currently-valid version number. So, it usually only installs them - when their ``setup.py`` directory is explicitly passed on the command line. - - However, if this option is used, then any in-development projects that were - installed using the ``setup.py develop`` command, will be used to build - eggs, effectively upgrading the "in-development" project to a snapshot - release. Normally, this option is used only in conjunction with the - ``--always-copy`` option to create a distributable snapshot of every egg - needed to run an application. - - Note that if you use this option, you must make sure that there is a valid - version number (such as an SVN revision number tag) for any in-development - projects that may be used, as otherwise EasyInstall may not be able to tell - what version of the project is "newer" when future installations or - upgrades are attempted. - - -.. _non-root installation: - -Custom Installation Locations ------------------------------ - -By default, EasyInstall installs python packages into Python's main ``site-packages`` directory, -and manages them using a custom ``.pth`` file in that same directory. - -Very often though, a user or developer wants ``easy_install`` to install and manage python packages -in an alternative location, usually for one of 3 reasons: - -1. They don't have access to write to the main Python site-packages directory. - -2. They want a user-specific stash of packages, that is not visible to other users. - -3. They want to isolate a set of packages to a specific python application, usually to minimize - the possibility of version conflicts. - -Historically, there have been many approaches to achieve custom installation. -The following section lists only the easiest and most relevant approaches [1]_. - -`Use the "--user" option`_ - -`Use the "--user" option and customize "PYTHONUSERBASE"`_ - -`Use "virtualenv"`_ - -.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_. - -.. _PEP-370: http://www.python.org/dev/peps/pep-0370/ - - -Use the "--user" option -~~~~~~~~~~~~~~~~~~~~~~~ -Python provides a User scheme for installation, which means that all -python distributions support an alternative install location that is specific to a user [3]_. -The Default location for each OS is explained in the python documentation -for the ``site.USER_BASE`` variable. This mode of installation can be turned on by -specifying the ``--user`` option to ``setup.py install`` or ``easy_install``. -This approach serves the need to have a user-specific stash of packages. - -.. [3] Prior to the User scheme, there was the Home scheme, which is still available, but requires more effort than the User scheme to get packages recognized. - -Use the "--user" option and customize "PYTHONUSERBASE" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The User scheme install location can be customized by setting the ``PYTHONUSERBASE`` environment -variable, which updates the value of ``site.USER_BASE``. To isolate packages to a specific -application, simply set the OS environment of that application to a specific value of -``PYTHONUSERBASE``, that contains just those packages. - -Use "virtualenv" -~~~~~~~~~~~~~~~~ -"virtualenv" is a 3rd-party python package that effectively "clones" a python installation, thereby -creating an isolated location to install packages. The evolution of "virtualenv" started before the existence -of the User installation scheme. "virtualenv" provides a version of ``easy_install`` that is -scoped to the cloned python install and is used in the normal way. "virtualenv" does offer various features -that the User installation scheme alone does not provide, e.g. the ability to hide the main python site-packages. - -Please refer to the `virtualenv`_ documentation for more details. - -.. _virtualenv: https://pypi.org/project/virtualenv/ - - - -Package Index "API" -------------------- - -Custom package indexes (and PyPI) must follow the following rules for -EasyInstall to be able to look up and download packages: - -1. Except where stated otherwise, "pages" are HTML or XHTML, and "links" - refer to ``href`` attributes. - -2. Individual project version pages' URLs must be of the form - ``base/projectname/version``, where ``base`` is the package index's base URL. - -3. Omitting the ``/version`` part of a project page's URL (but keeping the - trailing ``/``) should result in a page that is either: - - a) The single active version of that project, as though the version had been - explicitly included, OR - - b) A page with links to all of the active version pages for that project. - -4. Individual project version pages should contain direct links to downloadable - distributions where possible. It is explicitly permitted for a project's - "long_description" to include URLs, and these should be formatted as HTML - links by the package index, as EasyInstall does no special processing to - identify what parts of a page are index-specific and which are part of the - project's supplied description. - -5. Where available, MD5 information should be added to download URLs by - appending a fragment identifier of the form ``#md5=...``, where ``...`` is - the 32-character hex MD5 digest. EasyInstall will verify that the - downloaded file's MD5 digest matches the given value. - -6. Individual project version pages should identify any "homepage" or - "download" URLs using ``rel="homepage"`` and ``rel="download"`` attributes - on the HTML elements linking to those URLs. Use of these attributes will - cause EasyInstall to always follow the provided links, unless it can be - determined by inspection that they are downloadable distributions. If the - links are not to downloadable distributions, they are retrieved, and if they - are HTML, they are scanned for download links. They are *not* scanned for - additional "homepage" or "download" links, as these are only processed for - pages that are part of a package index site. - -7. The root URL of the index, if retrieved with a trailing ``/``, must result - in a page containing links to *all* projects' active version pages. - - (Note: This requirement is a workaround for the absence of case-insensitive - ``safe_name()`` matching of project names in URL paths. If project names are - matched in this fashion (e.g. via the PyPI server, mod_rewrite, or a similar - mechanism), then it is not necessary to include this all-packages listing - page.) - -8. If a package index is accessed via a ``file://`` URL, then EasyInstall will - automatically use ``index.html`` files, if present, when trying to read a - directory with a trailing ``/`` on the URL. diff --git a/docs/index.txt b/docs/index.txt index 13a46e74..c251260d 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,5 +21,4 @@ Documentation content: python3 development roadmap - Deprecated: Easy Install history diff --git a/easy_install.py b/easy_install.py deleted file mode 100644 index d87e9840..00000000 --- a/easy_install.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Run the EasyInstall command""" - -if __name__ == '__main__': - from setuptools.command.easy_install import main - main() diff --git a/setup.cfg b/setup.cfg index 42a3d86c..385ba14d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,6 @@ classifiers = [options] zip_safe = True python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* -py_modules = easy_install packages = find: [options.packages.find] diff --git a/setup.py b/setup.py index d97895fc..59efc237 100755 --- a/setup.py +++ b/setup.py @@ -31,22 +31,6 @@ def read_commands(): return command_ns['__all__'] -def _gen_console_scripts(): - yield "easy_install = setuptools.command.easy_install:main" - - # Gentoo distributions manage the python-version-specific scripts - # themselves, so those platforms define an environment variable to - # suppress the creation of the version-specific scripts. - var_names = ( - 'SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', - 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', - ) - if any(os.environ.get(var) not in (None, "", "0") for var in var_names): - return - tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main" - yield tmpl.format(shortver='{}.{}'.format(*sys.version_info)) - - package_data = dict( setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], ) @@ -125,9 +109,6 @@ setup_params = dict( "depends.txt = setuptools.command.egg_info:warn_depends_obsolete", "dependency_links.txt = setuptools.command.egg_info:overwrite_arg", ], - "console_scripts": list(_gen_console_scripts()), - "setuptools.installation": - ['eggsecutable = setuptools.command.easy_install:bootstrap'], }, dependency_links=[ pypi_link( diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 545c3c44..9d350ac0 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -73,7 +73,7 @@ warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) __all__ = [ 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', - 'main', 'get_exe_prefixes', + 'get_exe_prefixes', ] @@ -2283,59 +2283,6 @@ def current_umask(): return tmp -def bootstrap(): - # This function is called when setuptools*.egg is run using /bin/sh - import setuptools - - argv0 = os.path.dirname(setuptools.__path__[0]) - sys.argv[0] = argv0 - sys.argv.append(argv0) - main() - - -def main(argv=None, **kw): - from setuptools import setup - from setuptools.dist import Distribution - - class DistributionWithoutHelpCommands(Distribution): - common_usage = "" - - def _show_help(self, *args, **kw): - with _patch_usage(): - Distribution._show_help(self, *args, **kw) - - if argv is None: - argv = sys.argv[1:] - - with _patch_usage(): - setup( - script_args=['-q', 'easy_install', '-v'] + argv, - script_name=sys.argv[0] or 'easy_install', - distclass=DistributionWithoutHelpCommands, - **kw - ) - - -@contextlib.contextmanager -def _patch_usage(): - import distutils.core - USAGE = textwrap.dedent(""" - usage: %(script)s [options] requirement_or_url ... - or: %(script)s --help - """).lstrip() - - def gen_usage(script_name): - return USAGE % dict( - script=os.path.basename(script_name), - ) - - saved = distutils.core.gen_usage - distutils.core.gen_usage = gen_usage - try: - yield - finally: - distutils.core.gen_usage = saved - class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index aa75899a..68319c2f 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -467,22 +467,24 @@ class TestSetupRequires: """ monkeypatch.setenv(str('PIP_RETRIES'), str('0')) monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) - with contexts.quiet(): - # create an sdist that has a build-time dependency. - with TestSetupRequires.create_sdist() as dist_file: - with contexts.tempdir() as temp_install_dir: - with contexts.environment(PYTHONPATH=temp_install_dir): - ei_params = [ - '--index-url', mock_index.url, - '--exclude-scripts', - '--install-dir', temp_install_dir, - dist_file, - ] - with sandbox.save_argv(['easy_install']): - # attempt to install the dist. It should - # fail because it doesn't exist. - with pytest.raises(SystemExit): - easy_install_pkg.main(ei_params) + monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) + # create an sdist that has a build-time dependency. + with TestSetupRequires.create_sdist() as dist_file: + with contexts.tempdir() as temp_dir: + setup_py = os.path.join(temp_dir, 'setup.py') + with open(setup_py, 'w') as fp: + fp.write('__import__("setuptools").setup()') + temp_install_dir = os.path.join(temp_dir, 'target') + os.mkdir(temp_install_dir) + with contexts.environment(PYTHONPATH=temp_install_dir): + # attempt to install the dist. It should + # fail because it doesn't exist. + with pytest.raises(SystemExit): + run_setup(setup_py, ['easy_install', + '--exclude-scripts', + '--index-url', mock_index.url, + '--install-dir', temp_install_dir, + dist_file]) # there should have been one requests to the server assert [r.path for r in mock_index.requests] == ['/does-not-exist/'] diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index f937d981..3c5df68a 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -64,9 +64,8 @@ class TestNamespaces: target.mkdir() install_cmd = [ sys.executable, - '-m', 'easy_install', - '-d', str(target), - str(pkg), + '-m', 'pip.__main__', 'install', + '-t', str(target), str(pkg), ] with test.test.paths_on_pythonpath([str(target)]): subprocess.check_call(install_cmd) -- cgit v1.2.3 From b8101f06532b1deab448e6e23d0a61eb125c62df Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Thu, 14 Nov 2019 22:01:09 +0100 Subject: deprecate easy_install command --- setuptools/command/easy_install.py | 8 +++++++- setuptools/command/install.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 9d350ac0..d273bc10 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -410,7 +410,13 @@ class easy_install(Command): ] self._expand_attrs(dirs) - def run(self): + def run(self, show_deprecation=True): + if show_deprecation: + self.announce( + "WARNING: The easy_install command is deprecated " + "and will be removed in a future version." + , log.WARN, + ) if self.verbose != self.distribution.verbose: log.set_verbosity(self.verbose) try: diff --git a/setuptools/command/install.py b/setuptools/command/install.py index 31a5ddb5..72b9a3e4 100644 --- a/setuptools/command/install.py +++ b/setuptools/command/install.py @@ -114,7 +114,7 @@ class install(orig.install): args.insert(0, setuptools.bootstrap_install_from) cmd.args = args - cmd.run() + cmd.run(show_deprecation=False) setuptools.bootstrap_install_from = None -- cgit v1.2.3 From dc868755d53520895d96ec0251a66df562a37095 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Fri, 15 Nov 2019 12:03:53 +0100 Subject: drop support for Python 3.4 --- .travis.yml | 1 - changelog.d/1908.breaking.rst | 1 + docs/easy_install.txt | 2 +- pkg_resources/__init__.py | 4 ++-- setup.cfg | 3 +-- setuptools/tests/test_virtualenv.py | 5 +---- tests/requirements.txt | 3 +-- tox.ini | 2 +- 8 files changed, 8 insertions(+), 13 deletions(-) create mode 100644 changelog.d/1908.breaking.rst diff --git a/.travis.yml b/.travis.yml index 7088d166..3a744f23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ jobs: env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - python: pypy3 env: DISABLE_COVERAGE=1 - - python: 3.4 - python: 3.5 - &default_py python: 3.6 diff --git a/changelog.d/1908.breaking.rst b/changelog.d/1908.breaking.rst new file mode 100644 index 00000000..3fbb9fe7 --- /dev/null +++ b/changelog.d/1908.breaking.rst @@ -0,0 +1 @@ +Drop support for Python 3.4. diff --git a/docs/easy_install.txt b/docs/easy_install.txt index 544b9efd..fac7b8fc 100644 --- a/docs/easy_install.txt +++ b/docs/easy_install.txt @@ -41,7 +41,7 @@ Please see the `setuptools PyPI page `_ for download links and basic installation instructions for each of the supported platforms. -You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be +You will need at least Python 3.5 or 2.7. An ``easy_install`` script will be installed in the normal location for Python scripts on your platform. Note that the instructions on the setuptools PyPI page assume that you are diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 51fb1192..2f5aa64a 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -88,8 +88,8 @@ __import__('pkg_resources.extern.packaging.markers') __metaclass__ = type -if (3, 0) < sys.version_info < (3, 4): - raise RuntimeError("Python 3.4 or later is required") +if (3, 0) < sys.version_info < (3, 5): + raise RuntimeError("Python 3.5 or later is required") if six.PY2: # Those builtin exceptions are only defined in Python 3 diff --git a/setup.cfg b/setup.cfg index 42a3d86c..920a2719 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,6 @@ classifiers = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 @@ -50,7 +49,7 @@ classifiers = [options] zip_safe = True -python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* +python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* py_modules = easy_install packages = find: diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 74a1284c..b60df32f 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -77,12 +77,9 @@ def _get_pip_versions(): 'pip==10.0.1', 'pip==18.1', 'pip==19.0.1', + 'https://github.com/pypa/pip/archive/master.zip', ] - # Pip's master dropped support for 3.4. - if not six.PY34: - network_versions.append('https://github.com/pypa/pip/archive/master.zip') - versions = [None] + [ pytest.param(v, **({} if network else {'marks': pytest.mark.skip})) for v in network_versions diff --git a/tests/requirements.txt b/tests/requirements.txt index 1f70adee..ff596773 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,5 @@ mock -pytest-flake8; python_version!="3.4" -pytest-flake8<=1.0.0; python_version=="3.4" +pytest-flake8 virtualenv>=13.0.0 pytest-virtualenv>=1.2.7 pytest>=3.7 diff --git a/tox.ini b/tox.ini index 5d439cb3..1eae7a7a 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # # To run Tox against all supported Python interpreters, you can set: # -# export TOXENV='py27,py3{4,5,6},pypy,pypy3' +# export TOXENV='py27,py3{5,6,7,8},pypy,pypy3' [tox] envlist=python -- cgit v1.2.3 From 6b210c65938527a4bbcea34942fe43971be3c014 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 16 Nov 2019 12:06:47 -0500 Subject: Move all finalization of distribution options into hooks. Allow hooks to specify an index for ordering. --- setup.py | 7 +++++++ setuptools/dist.py | 24 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index f5030dd6..ac56a1b0 100755 --- a/setup.py +++ b/setup.py @@ -89,6 +89,13 @@ setup_params = dict( "%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals() for cmd in read_commands() ], + "setuptools.finalize_distribution_options": [ + "parent_finalize = setuptools.dist:_Distribution.finalize_options", + "features = setuptools.dist:Distribution._finalize_feature_opts", + "keywords = setuptools.dist:Distribution._finalize_setup_keywords", + "2to3_doctests = " + "setuptools.dist:Distribution._finalize_2to3_doctests", + ], "distutils.setup_keywords": [ "eager_resources = setuptools.dist:assert_string_list", "namespace_packages = setuptools.dist:check_nsp", diff --git a/setuptools/dist.py b/setuptools/dist.py index 987d684e..44990431 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -724,19 +724,28 @@ class Distribution(_Distribution): return resolved_dists def finalize_options(self): - _Distribution.finalize_options(self) - if self.features: - self._set_global_opts_from_features() - + """ + Allow plugins to apply arbitrary operations to the + distribution. Each hook may optionally define a 'order' + to influence the order of execution. Smaller numbers + go first and the default is 0. + """ hook_key = 'setuptools.finalize_distribution_options' - for ep in pkg_resources.iter_entry_points(hook_key): + + def by_order(hook): + return getattr(hook, 'order', 0) + eps = pkg_resources.iter_entry_points(hook_key) + for ep in sorted(eps, key=by_order): ep.load()(self) + def _finalize_setup_keywords(self): for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): value = getattr(self, ep.name, None) if value is not None: ep.require(installer=self.fetch_build_egg) ep.load()(self, ep.name, value) + + def _finalize_2to3_doctests(self): if getattr(self, 'convert_2to3_doctests', None): # XXX may convert to set here when we can rely on set being builtin self.convert_2to3_doctests = [ @@ -790,9 +799,12 @@ class Distribution(_Distribution): cmd.ensure_finalized() return cmd.easy_install(req) - def _set_global_opts_from_features(self): + def _finalize_feature_opts(self): """Add --with-X/--without-X options based on optional features""" + if not self.features: + return + go = [] no = self.negative_opt.copy() -- cgit v1.2.3 From 88951e9a5633abd4cdbfa3584647cec4247b8c30 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 16 Nov 2019 12:47:35 -0500 Subject: Add changelog entry and documentation about the feature. --- changelog.d/1877.change.rst | 1 + docs/setuptools.txt | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 changelog.d/1877.change.rst diff --git a/changelog.d/1877.change.rst b/changelog.d/1877.change.rst new file mode 100644 index 00000000..5a744fa3 --- /dev/null +++ b/changelog.d/1877.change.rst @@ -0,0 +1 @@ +Setuptools now exposes a new entry point hook "setuptools.finalize_distribution_options", enabling plugins like `setuptools_scm `_ to configure options on the distribution at finalization time. diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 2e7fe3bd..ccbf069b 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1225,7 +1225,7 @@ the quoted part. Distributing a ``setuptools``-based project =========================================== -Detailed instructions to distribute a setuptools project can be found at +Detailed instructions to distribute a setuptools project can be found at `Packaging project tutorials`_. .. _Packaging project tutorials: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives @@ -1241,7 +1241,7 @@ setup.py is located:: This will generate distribution archives in the `dist` directory. -Before you upload the generated archives make sure you're registered on +Before you upload the generated archives make sure you're registered on https://test.pypi.org/account/register/. You will also need to verify your email to be able to upload any packages. You should install twine to be able to upload packages:: @@ -1264,11 +1264,11 @@ Distributing legacy ``setuptools`` projects using ez_setup.py .. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support. -Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is +Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is deprecated in favor of PIP. Please consider migrating to using pip and twine based distribution. -However, if you still have any ``ez_setup`` based packages, documentation for +However, if you still have any ``ez_setup`` based packages, documentation for ez_setup based distributions can be found at `ez_setup distribution guide`_. .. _ez_setup distribution guide: ez_setup.html @@ -2515,6 +2515,10 @@ script defines entry points for them! Adding ``setup()`` Arguments ---------------------------- +.. warning:: Adding arguments to setup is discouraged as such arguments + are only supported through imperative execution and not supported through + declarative config. + Sometimes, your commands may need additional arguments to the ``setup()`` call. You can enable this by defining entry points in the ``distutils.setup_keywords`` group. For example, if you wanted a ``setup()`` @@ -2566,6 +2570,25 @@ script using your extension lists your project in its ``setup_requires`` argument. +Customizing Distribution Options +-------------------------------- + +Plugins may wish to extend or alter the options on a Distribution object to +suit the purposes of that project. For example, a tool that infers the +``Distribution.version`` from SCM-metadata may need to hook into the +option finalization. To enable this feature, Setuptools offers an entry +point "setuptools.finalize_distribution_options". That entry point must +be a callable taking one argument (the Distribution instance). + +If the callable has an ``.order`` property, that value will be used to +determine the order in which the hook is called. Lower numbers are called +first and the default is zero (0). + +Plugins may read, alter, and set properties on the distribution, but each +plugin is encouraged to load the configuration/settings for their behavior +independently. + + Adding new EGG-INFO Files ------------------------- -- cgit v1.2.3 From a00798264cf4d55db28585cfc147050a4d579b52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 16 Nov 2019 12:49:03 -0500 Subject: Trim excess whitespace --- docs/setuptools.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 399a56d3..b7fdf410 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1207,7 +1207,7 @@ the quoted part. Distributing a ``setuptools``-based project =========================================== -Detailed instructions to distribute a setuptools project can be found at +Detailed instructions to distribute a setuptools project can be found at `Packaging project tutorials`_. .. _Packaging project tutorials: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives @@ -1223,7 +1223,7 @@ setup.py is located:: This will generate distribution archives in the `dist` directory. -Before you upload the generated archives make sure you're registered on +Before you upload the generated archives make sure you're registered on https://test.pypi.org/account/register/. You will also need to verify your email to be able to upload any packages. You should install twine to be able to upload packages:: -- cgit v1.2.3 From a1e956b20f11f2d02f5a9855bda37660080184c9 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sat, 16 Nov 2019 23:30:10 +0100 Subject: Revert "drop easy_install script and associated documentation" This reverts commit 6e1838a9fb5feb000ba9b6a3c37c8b39d7e872b3. --- docs/easy_install.txt | 1085 +++++++++++++++++++++++++++++++++ docs/index.txt | 1 + easy_install.py | 5 + setup.cfg | 1 + setup.py | 19 + setuptools/command/easy_install.py | 55 +- setuptools/tests/test_easy_install.py | 34 +- setuptools/tests/test_namespaces.py | 5 +- 8 files changed, 1184 insertions(+), 21 deletions(-) create mode 100644 docs/easy_install.txt create mode 100644 easy_install.py diff --git a/docs/easy_install.txt b/docs/easy_install.txt new file mode 100644 index 00000000..544b9efd --- /dev/null +++ b/docs/easy_install.txt @@ -0,0 +1,1085 @@ +============ +Easy Install +============ + +.. warning:: + Easy Install is deprecated. Do not use it. Instead use pip. If + you think you need Easy Install, please reach out to the PyPA + team (a ticket to pip or setuptools is fine), describing your + use-case. + +Easy Install is a python module (``easy_install``) bundled with ``setuptools`` +that lets you automatically download, build, install, and manage Python +packages. + +Please share your experiences with us! If you encounter difficulty installing +a package, please contact us via the `distutils mailing list +`_. (Note: please DO NOT send +private email directly to the author of setuptools; it will be discarded. The +mailing list is a searchable archive of previously-asked and answered +questions; you should begin your research there before reporting something as a +bug -- and then do so via list discussion first.) + +(Also, if you'd like to learn about how you can use ``setuptools`` to make your +own packages work better with EasyInstall, or provide EasyInstall-like features +without requiring your users to use EasyInstall directly, you'll probably want +to check out the full documentation as well.) + +.. contents:: **Table of Contents** + + +Using "Easy Install" +==================== + + +.. _installation instructions: + +Installing "Easy Install" +------------------------- + +Please see the `setuptools PyPI page `_ +for download links and basic installation instructions for each of the +supported platforms. + +You will need at least Python 3.4 or 2.7. An ``easy_install`` script will be +installed in the normal location for Python scripts on your platform. + +Note that the instructions on the setuptools PyPI page assume that you are +are installing to Python's primary ``site-packages`` directory. If this is +not the case, you should consult the section below on `Custom Installation +Locations`_ before installing. (And, on Windows, you should not use the +``.exe`` installer when installing to an alternate location.) + +Note that ``easy_install`` normally works by downloading files from the +internet. If you are behind an NTLM-based firewall that prevents Python +programs from accessing the net directly, you may wish to first install and use +the `APS proxy server `_, which lets you get past such +firewalls in the same way that your web browser(s) do. + +(Alternately, if you do not wish easy_install to actually download anything, you +can restrict it from doing so with the ``--allow-hosts`` option; see the +sections on `restricting downloads with --allow-hosts`_ and `command-line +options`_ for more details.) + + +Troubleshooting +~~~~~~~~~~~~~~~ + +If EasyInstall/setuptools appears to install correctly, and you can run the +``easy_install`` command but it fails with an ``ImportError``, the most likely +cause is that you installed to a location other than ``site-packages``, +without taking any of the steps described in the `Custom Installation +Locations`_ section below. Please see that section and follow the steps to +make sure that your custom location will work correctly. Then re-install. + +Similarly, if you can run ``easy_install``, and it appears to be installing +packages, but then you can't import them, the most likely issue is that you +installed EasyInstall correctly but are using it to install packages to a +non-standard location that hasn't been properly prepared. Again, see the +section on `Custom Installation Locations`_ for more details. + + +Windows Notes +~~~~~~~~~~~~~ + +Installing setuptools will provide an ``easy_install`` command according to +the techniques described in `Executables and Launchers`_. If the +``easy_install`` command is not available after installation, that section +provides details on how to configure Windows to make the commands available. + + +Downloading and Installing a Package +------------------------------------ + +For basic use of ``easy_install``, you need only supply the filename or URL of +a source distribution or .egg file (`Python Egg`__). + +__ http://peak.telecommunity.com/DevCenter/PythonEggs + +**Example 1**. Install a package by name, searching PyPI for the latest +version, and automatically downloading, building, and installing it:: + + easy_install SQLObject + +**Example 2**. Install or upgrade a package by name and version by finding +links on a given "download page":: + + easy_install -f http://pythonpaste.org/package_index.html SQLObject + +**Example 3**. Download a source distribution from a specified URL, +automatically building and installing it:: + + easy_install http://example.com/path/to/MyPackage-1.2.3.tgz + +**Example 4**. Install an already-downloaded .egg file:: + + easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg + +**Example 5**. Upgrade an already-installed package to the latest version +listed on PyPI:: + + easy_install --upgrade PyProtocols + +**Example 6**. Install a source distribution that's already downloaded and +extracted in the current directory (New in 0.5a9):: + + easy_install . + +**Example 7**. (New in 0.6a1) Find a source distribution or Subversion +checkout URL for a package, and extract it or check it out to +``~/projects/sqlobject`` (the name will always be in all-lowercase), where it +can be examined or edited. (The package will not be installed, but it can +easily be installed with ``easy_install ~/projects/sqlobject``. See `Editing +and Viewing Source Packages`_ below for more info.):: + + easy_install --editable --build-directory ~/projects SQLObject + +**Example 7**. (New in 0.6.11) Install a distribution within your home dir:: + + easy_install --user SQLAlchemy + +Easy Install accepts URLs, filenames, PyPI package names (i.e., ``distutils`` +"distribution" names), and package+version specifiers. In each case, it will +attempt to locate the latest available version that meets your criteria. + +When downloading or processing downloaded files, Easy Install recognizes +distutils source distribution files with extensions of .tgz, .tar, .tar.gz, +.tar.bz2, or .zip. And of course it handles already-built .egg +distributions as well as ``.win32.exe`` installers built using distutils. + +By default, packages are installed to the running Python installation's +``site-packages`` directory, unless you provide the ``-d`` or ``--install-dir`` +option to specify an alternative directory, or specify an alternate location +using distutils configuration files. (See `Configuration Files`_, below.) + +By default, any scripts included with the package are installed to the running +Python installation's standard script installation location. However, if you +specify an installation directory via the command line or a config file, then +the default directory for installing scripts will be the same as the package +installation directory, to ensure that the script will have access to the +installed package. You can override this using the ``-s`` or ``--script-dir`` +option. + +Installed packages are added to an ``easy-install.pth`` file in the install +directory, so that Python will always use the most-recently-installed version +of the package. If you would like to be able to select which version to use at +runtime, you should use the ``-m`` or ``--multi-version`` option. + + +Upgrading a Package +------------------- + +You don't need to do anything special to upgrade a package: just install the +new version, either by requesting a specific version, e.g.:: + + easy_install "SomePackage==2.0" + +a version greater than the one you have now:: + + easy_install "SomePackage>2.0" + +using the upgrade flag, to find the latest available version on PyPI:: + + easy_install --upgrade SomePackage + +or by using a download page, direct download URL, or package filename:: + + easy_install -f http://example.com/downloads ExamplePackage + + easy_install http://example.com/downloads/ExamplePackage-2.0-py2.4.egg + + easy_install my_downloads/ExamplePackage-2.0.tgz + +If you're using ``-m`` or ``--multi-version`` , using the ``require()`` +function at runtime automatically selects the newest installed version of a +package that meets your version criteria. So, installing a newer version is +the only step needed to upgrade such packages. + +If you're installing to a directory on PYTHONPATH, or a configured "site" +directory (and not using ``-m``), installing a package automatically replaces +any previous version in the ``easy-install.pth`` file, so that Python will +import the most-recently installed version by default. So, again, installing +the newer version is the only upgrade step needed. + +If you haven't suppressed script installation (using ``--exclude-scripts`` or +``-x``), then the upgraded version's scripts will be installed, and they will +be automatically patched to ``require()`` the corresponding version of the +package, so that you can use them even if they are installed in multi-version +mode. + +``easy_install`` never actually deletes packages (unless you're installing a +package with the same name and version number as an existing package), so if +you want to get rid of older versions of a package, please see `Uninstalling +Packages`_, below. + + +Changing the Active Version +--------------------------- + +If you've upgraded a package, but need to revert to a previously-installed +version, you can do so like this:: + + easy_install PackageName==1.2.3 + +Where ``1.2.3`` is replaced by the exact version number you wish to switch to. +If a package matching the requested name and version is not already installed +in a directory on ``sys.path``, it will be located via PyPI and installed. + +If you'd like to switch to the latest installed version of ``PackageName``, you +can do so like this:: + + easy_install PackageName + +This will activate the latest installed version. (Note: if you have set any +``find_links`` via distutils configuration files, those download pages will be +checked for the latest available version of the package, and it will be +downloaded and installed if it is newer than your current version.) + +Note that changing the active version of a package will install the newly +active version's scripts, unless the ``--exclude-scripts`` or ``-x`` option is +specified. + + +Uninstalling Packages +--------------------- + +If you have replaced a package with another version, then you can just delete +the package(s) you don't need by deleting the PackageName-versioninfo.egg file +or directory (found in the installation directory). + +If you want to delete the currently installed version of a package (or all +versions of a package), you should first run:: + + easy_install -m PackageName + +This will ensure that Python doesn't continue to search for a package you're +planning to remove. After you've done this, you can safely delete the .egg +files or directories, along with any scripts you wish to remove. + + +Managing Scripts +---------------- + +Whenever you install, upgrade, or change versions of a package, EasyInstall +automatically installs the scripts for the selected package version, unless +you tell it not to with ``-x`` or ``--exclude-scripts``. If any scripts in +the script directory have the same name, they are overwritten. + +Thus, you do not normally need to manually delete scripts for older versions of +a package, unless the newer version of the package does not include a script +of the same name. However, if you are completely uninstalling a package, you +may wish to manually delete its scripts. + +EasyInstall's default behavior means that you can normally only run scripts +from one version of a package at a time. If you want to keep multiple versions +of a script available, however, you can simply use the ``--multi-version`` or +``-m`` option, and rename the scripts that EasyInstall creates. This works +because EasyInstall installs scripts as short code stubs that ``require()`` the +matching version of the package the script came from, so renaming the script +has no effect on what it executes. + +For example, suppose you want to use two versions of the ``rst2html`` tool +provided by the `docutils `_ package. You might +first install one version:: + + easy_install -m docutils==0.3.9 + +then rename the ``rst2html.py`` to ``r2h_039``, and install another version:: + + easy_install -m docutils==0.3.10 + +This will create another ``rst2html.py`` script, this one using docutils +version 0.3.10 instead of 0.3.9. You now have two scripts, each using a +different version of the package. (Notice that we used ``-m`` for both +installations, so that Python won't lock us out of using anything but the most +recently-installed version of the package.) + + +Executables and Launchers +------------------------- + +On Unix systems, scripts are installed with as natural files with a "#!" +header and no extension and they launch under the Python version indicated in +the header. + +On Windows, there is no mechanism to "execute" files without extensions, so +EasyInstall provides two techniques to mirror the Unix behavior. The behavior +is indicated by the SETUPTOOLS_LAUNCHER environment variable, which may be +"executable" (default) or "natural". + +Regardless of the technique used, the script(s) will be installed to a Scripts +directory (by default in the Python installation directory). It is recommended +for EasyInstall that you ensure this directory is in the PATH environment +variable. The easiest way to ensure the Scripts directory is in the PATH is +to run ``Tools\Scripts\win_add2path.py`` from the Python directory. + +Note that instead of changing your ``PATH`` to include the Python scripts +directory, you can also retarget the installation location for scripts so they +go on a directory that's already on the ``PATH``. For more information see +`Command-Line Options`_ and `Configuration Files`_. During installation, +pass command line options (such as ``--script-dir``) to control where +scripts will be installed. + + +Windows Executable Launcher +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the "executable" launcher is used, EasyInstall will create a '.exe' +launcher of the same name beside each installed script (including +``easy_install`` itself). These small .exe files launch the script of the +same name using the Python version indicated in the '#!' header. + +This behavior is currently default. To force +the use of executable launchers, set ``SETUPTOOLS_LAUNCHER`` to "executable". + +Natural Script Launcher +~~~~~~~~~~~~~~~~~~~~~~~ + +EasyInstall also supports deferring to an external launcher such as +`pylauncher `_ for launching scripts. +Enable this experimental functionality by setting the +``SETUPTOOLS_LAUNCHER`` environment variable to "natural". EasyInstall will +then install scripts as simple +scripts with a .pya (or .pyw) extension appended. If these extensions are +associated with the pylauncher and listed in the PATHEXT environment variable, +these scripts can then be invoked simply and directly just like any other +executable. This behavior may become default in a future version. + +EasyInstall uses the .pya extension instead of simply +the typical '.py' extension. This distinct extension is necessary to prevent +Python +from treating the scripts as importable modules (where name conflicts exist). +Current releases of pylauncher do not yet associate with .pya files by +default, but future versions should do so. + + +Tips & Techniques +----------------- + +Multiple Python Versions +~~~~~~~~~~~~~~~~~~~~~~~~ + +EasyInstall installs itself under two names: +``easy_install`` and ``easy_install-N.N``, where ``N.N`` is the Python version +used to install it. Thus, if you install EasyInstall for both Python 3.2 and +2.7, you can use the ``easy_install-3.2`` or ``easy_install-2.7`` scripts to +install packages for the respective Python version. + +Setuptools also supplies easy_install as a runnable module which may be +invoked using ``python -m easy_install`` for any Python with Setuptools +installed. + +Restricting Downloads with ``--allow-hosts`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``--allow-hosts`` (``-H``) option to restrict what domains +EasyInstall will look for links and downloads on. ``--allow-hosts=None`` +prevents downloading altogether. You can also use wildcards, for example +to restrict downloading to hosts in your own intranet. See the section below +on `Command-Line Options`_ for more details on the ``--allow-hosts`` option. + +By default, there are no host restrictions in effect, but you can change this +default by editing the appropriate `configuration files`_ and adding: + +.. code-block:: ini + + [easy_install] + allow_hosts = *.myintranet.example.com,*.python.org + +The above example would then allow downloads only from hosts in the +``python.org`` and ``myintranet.example.com`` domains, unless overridden on the +command line. + + +Installing on Un-networked Machines +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Just copy the eggs or source packages you need to a directory on the target +machine, then use the ``-f`` or ``--find-links`` option to specify that +directory's location. For example:: + + easy_install -H None -f somedir SomePackage + +will attempt to install SomePackage using only eggs and source packages found +in ``somedir`` and disallowing all remote access. You should of course make +sure you have all of SomePackage's dependencies available in somedir. + +If you have another machine of the same operating system and library versions +(or if the packages aren't platform-specific), you can create the directory of +eggs using a command like this:: + + easy_install -zmaxd somedir SomePackage + +This will tell EasyInstall to put zipped eggs or source packages for +SomePackage and all its dependencies into ``somedir``, without creating any +scripts or .pth files. You can then copy the contents of ``somedir`` to the +target machine. (``-z`` means zipped eggs, ``-m`` means multi-version, which +prevents .pth files from being used, ``-a`` means to copy all the eggs needed, +even if they're installed elsewhere on the machine, and ``-d`` indicates the +directory to place the eggs in.) + +You can also build the eggs from local development packages that were installed +with the ``setup.py develop`` command, by including the ``-l`` option, e.g.:: + + easy_install -zmaxld somedir SomePackage + +This will use locally-available source distributions to build the eggs. + + +Packaging Others' Projects As Eggs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Need to distribute a package that isn't published in egg form? You can use +EasyInstall to build eggs for a project. You'll want to use the ``--zip-ok``, +``--exclude-scripts``, and possibly ``--no-deps`` options (``-z``, ``-x`` and +``-N``, respectively). Use ``-d`` or ``--install-dir`` to specify the location +where you'd like the eggs placed. By placing them in a directory that is +published to the web, you can then make the eggs available for download, either +in an intranet or to the internet at large. + +If someone distributes a package in the form of a single ``.py`` file, you can +wrap it in an egg by tacking an ``#egg=name-version`` suffix on the file's URL. +So, something like this:: + + easy_install -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo + +will install the package as an egg, and this:: + + easy_install -zmaxd. \ + -f "http://some.example.com/downloads/foo.py#egg=foo-1.0" foo + +will create a ``.egg`` file in the current directory. + + +Creating your own Package Index +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to local directories and the Python Package Index, EasyInstall can +find download links on most any web page whose URL is given to the ``-f`` +(``--find-links``) option. In the simplest case, you can simply have a web +page with links to eggs or Python source packages, even an automatically +generated directory listing (such as the Apache web server provides). + +If you are setting up an intranet site for package downloads, you may want to +configure the target machines to use your download site by default, adding +something like this to their `configuration files`_: + +.. code-block:: ini + + [easy_install] + find_links = http://mypackages.example.com/somedir/ + http://turbogears.org/download/ + http://peak.telecommunity.com/dist/ + +As you can see, you can list multiple URLs separated by whitespace, continuing +on multiple lines if necessary (as long as the subsequent lines are indented. + +If you are more ambitious, you can also create an entirely custom package index +or PyPI mirror. See the ``--index-url`` option under `Command-Line Options`_, +below, and also the section on `Package Index "API"`_. + + +Password-Protected Sites +------------------------ + +If a site you want to download from is password-protected using HTTP "Basic" +authentication, you can specify your credentials in the URL, like so:: + + http://some_userid:some_password@some.example.com/some_path/ + +You can do this with both index page URLs and direct download URLs. As long +as any HTML pages read by easy_install use *relative* links to point to the +downloads, the same user ID and password will be used to do the downloading. + +Using .pypirc Credentials +------------------------- + +In additional to supplying credentials in the URL, ``easy_install`` will also +honor credentials if present in the .pypirc file. Teams maintaining a private +repository of packages may already have defined access credentials for +uploading packages according to the distutils documentation. ``easy_install`` +will attempt to honor those if present. Refer to the distutils documentation +for Python 2.5 or later for details on the syntax. + +Controlling Build Options +~~~~~~~~~~~~~~~~~~~~~~~~~ + +EasyInstall respects standard distutils `Configuration Files`_, so you can use +them to configure build options for packages that it installs from source. For +example, if you are on Windows using the MinGW compiler, you can configure the +default compiler by putting something like this: + +.. code-block:: ini + + [build] + compiler = mingw32 + +into the appropriate distutils configuration file. In fact, since this is just +normal distutils configuration, it will affect any builds using that config +file, not just ones done by EasyInstall. For example, if you add those lines +to ``distutils.cfg`` in the ``distutils`` package directory, it will be the +default compiler for *all* packages you build. See `Configuration Files`_ +below for a list of the standard configuration file locations, and links to +more documentation on using distutils configuration files. + + +Editing and Viewing Source Packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes a package's source distribution contains additional documentation, +examples, configuration files, etc., that are not part of its actual code. If +you want to be able to examine these files, you can use the ``--editable`` +option to EasyInstall, and EasyInstall will look for a source distribution +or Subversion URL for the package, then download and extract it or check it out +as a subdirectory of the ``--build-directory`` you specify. If you then wish +to install the package after editing or configuring it, you can do so by +rerunning EasyInstall with that directory as the target. + +Note that using ``--editable`` stops EasyInstall from actually building or +installing the package; it just finds, obtains, and possibly unpacks it for +you. This allows you to make changes to the package if necessary, and to +either install it in development mode using ``setup.py develop`` (if the +package uses setuptools, that is), or by running ``easy_install projectdir`` +(where ``projectdir`` is the subdirectory EasyInstall created for the +downloaded package. + +In order to use ``--editable`` (``-e`` for short), you *must* also supply a +``--build-directory`` (``-b`` for short). The project will be placed in a +subdirectory of the build directory. The subdirectory will have the same +name as the project itself, but in all-lowercase. If a file or directory of +that name already exists, EasyInstall will print an error message and exit. + +Also, when using ``--editable``, you cannot use URLs or filenames as arguments. +You *must* specify project names (and optional version requirements) so that +EasyInstall knows what directory name(s) to create. If you need to force +EasyInstall to use a particular URL or filename, you should specify it as a +``--find-links`` item (``-f`` for short), and then also specify +the project name, e.g.:: + + easy_install -eb ~/projects \ + -fhttp://prdownloads.sourceforge.net/ctypes/ctypes-0.9.6.tar.gz?download \ + ctypes==0.9.6 + + +Dealing with Installation Conflicts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(NOTE: As of 0.6a11, this section is obsolete; it is retained here only so that +people using older versions of EasyInstall can consult it. As of version +0.6a11, installation conflicts are handled automatically without deleting the +old or system-installed packages, and without ignoring the issue. Instead, +eggs are automatically shifted to the front of ``sys.path`` using special +code added to the ``easy-install.pth`` file. So, if you are using version +0.6a11 or better of setuptools, you do not need to worry about conflicts, +and the following issues do not apply to you.) + +EasyInstall installs distributions in a "managed" way, such that each +distribution can be independently activated or deactivated on ``sys.path``. +However, packages that were not installed by EasyInstall are "unmanaged", +in that they usually live all in one directory and cannot be independently +activated or deactivated. + +As a result, if you are using EasyInstall to upgrade an existing package, or +to install a package with the same name as an existing package, EasyInstall +will warn you of the conflict. (This is an improvement over ``setup.py +install``, because the ``distutils`` just install new packages on top of old +ones, possibly combining two unrelated packages or leaving behind modules that +have been deleted in the newer version of the package.) + +EasyInstall will stop the installation if it detects a conflict +between an existing, "unmanaged" package, and a module or package in any of +the distributions you're installing. It will display a list of all of the +existing files and directories that would need to be deleted for the new +package to be able to function correctly. To proceed, you must manually +delete these conflicting files and directories and re-run EasyInstall. + +Of course, once you've replaced all of your existing "unmanaged" packages with +versions managed by EasyInstall, you won't have any more conflicts to worry +about! + + +Compressed Installation +~~~~~~~~~~~~~~~~~~~~~~~ + +EasyInstall tries to install packages in zipped form, if it can. Zipping +packages can improve Python's overall import performance if you're not using +the ``--multi-version`` option, because Python processes zipfile entries on +``sys.path`` much faster than it does directories. + +As of version 0.5a9, EasyInstall analyzes packages to determine whether they +can be safely installed as a zipfile, and then acts on its analysis. (Previous +versions would not install a package as a zipfile unless you used the +``--zip-ok`` option.) + +The current analysis approach is fairly conservative; it currently looks for: + + * Any use of the ``__file__`` or ``__path__`` variables (which should be + replaced with ``pkg_resources`` API calls) + + * Possible use of ``inspect`` functions that expect to manipulate source files + (e.g. ``inspect.getsource()``) + + * Top-level modules that might be scripts used with ``python -m`` (Python 2.4) + +If any of the above are found in the package being installed, EasyInstall will +assume that the package cannot be safely run from a zipfile, and unzip it to +a directory instead. You can override this analysis with the ``-zip-ok`` flag, +which will tell EasyInstall to install the package as a zipfile anyway. Or, +you can use the ``--always-unzip`` flag, in which case EasyInstall will always +unzip, even if its analysis says the package is safe to run as a zipfile. + +Normally, however, it is simplest to let EasyInstall handle the determination +of whether to zip or unzip, and only specify overrides when needed to work +around a problem. If you find you need to override EasyInstall's guesses, you +may want to contact the package author and the EasyInstall maintainers, so that +they can make appropriate changes in future versions. + +(Note: If a package uses ``setuptools`` in its setup script, the package author +has the option to declare the package safe or unsafe for zipped usage via the +``zip_safe`` argument to ``setup()``. If the package author makes such a +declaration, EasyInstall believes the package's author and does not perform its +own analysis. However, your command-line option, if any, will still override +the package author's choice.) + + +Reference Manual +================ + +Configuration Files +------------------- + +(New in 0.4a2) + +You may specify default options for EasyInstall using the standard +distutils configuration files, under the command heading ``easy_install``. +EasyInstall will look first for a ``setup.cfg`` file in the current directory, +then a ``~/.pydistutils.cfg`` or ``$HOME\\pydistutils.cfg`` (on Unix-like OSes +and Windows, respectively), and finally a ``distutils.cfg`` file in the +``distutils`` package directory. Here's a simple example: + +.. code-block:: ini + + [easy_install] + + # set the default location to install packages + install_dir = /home/me/lib/python + + # Notice that indentation can be used to continue an option + # value; this is especially useful for the "--find-links" + # option, which tells easy_install to use download links on + # these pages before consulting PyPI: + # + find_links = http://sqlobject.org/ + http://peak.telecommunity.com/dist/ + +In addition to accepting configuration for its own options under +``[easy_install]``, EasyInstall also respects defaults specified for other +distutils commands. For example, if you don't set an ``install_dir`` for +``[easy_install]``, but *have* set an ``install_lib`` for the ``[install]`` +command, this will become EasyInstall's default installation directory. Thus, +if you are already using distutils configuration files to set default install +locations, build options, etc., EasyInstall will respect your existing settings +until and unless you override them explicitly in an ``[easy_install]`` section. + +For more information, see also the current Python documentation on the `use and +location of distutils configuration files `_. + +Notice that ``easy_install`` will use the ``setup.cfg`` from the current +working directory only if it was triggered from ``setup.py`` through the +``install_requires`` option. The standalone command will not use that file. + +Command-Line Options +-------------------- + +``--zip-ok, -z`` + Install all packages as zip files, even if they are marked as unsafe for + running as a zipfile. This can be useful when EasyInstall's analysis + of a non-setuptools package is too conservative, but keep in mind that + the package may not work correctly. (Changed in 0.5a9; previously this + option was required in order for zipped installation to happen at all.) + +``--always-unzip, -Z`` + Don't install any packages as zip files, even if the packages are marked + as safe for running as a zipfile. This can be useful if a package does + something unsafe, but not in a way that EasyInstall can easily detect. + EasyInstall's default analysis is currently very conservative, however, so + you should only use this option if you've had problems with a particular + package, and *after* reporting the problem to the package's maintainer and + to the EasyInstall maintainers. + + (Note: the ``-z/-Z`` options only affect the installation of newly-built + or downloaded packages that are not already installed in the target + directory; if you want to convert an existing installed version from + zipped to unzipped or vice versa, you'll need to delete the existing + version first, and re-run EasyInstall.) + +``--multi-version, -m`` + "Multi-version" mode. Specifying this option prevents ``easy_install`` from + adding an ``easy-install.pth`` entry for the package being installed, and + if an entry for any version the package already exists, it will be removed + upon successful installation. In multi-version mode, no specific version of + the package is available for importing, unless you use + ``pkg_resources.require()`` to put it on ``sys.path``. This can be as + simple as:: + + from pkg_resources import require + require("SomePackage", "OtherPackage", "MyPackage") + + which will put the latest installed version of the specified packages on + ``sys.path`` for you. (For more advanced uses, like selecting specific + versions and enabling optional dependencies, see the ``pkg_resources`` API + doc.) + + Changed in 0.6a10: this option is no longer silently enabled when + installing to a non-PYTHONPATH, non-"site" directory. You must always + explicitly use this option if you want it to be active. + +``--upgrade, -U`` (New in 0.5a4) + By default, EasyInstall only searches online if a project/version + requirement can't be met by distributions already installed + on sys.path or the installation directory. However, if you supply the + ``--upgrade`` or ``-U`` flag, EasyInstall will always check the package + index and ``--find-links`` URLs before selecting a version to install. In + this way, you can force EasyInstall to use the latest available version of + any package it installs (subject to any version requirements that might + exclude such later versions). + +``--install-dir=DIR, -d DIR`` + Set the installation directory. It is up to you to ensure that this + directory is on ``sys.path`` at runtime, and to use + ``pkg_resources.require()`` to enable the installed package(s) that you + need. + + (New in 0.4a2) If this option is not directly specified on the command line + or in a distutils configuration file, the distutils default installation + location is used. Normally, this would be the ``site-packages`` directory, + but if you are using distutils configuration files, setting things like + ``prefix`` or ``install_lib``, then those settings are taken into + account when computing the default installation directory, as is the + ``--prefix`` option. + +``--script-dir=DIR, -s DIR`` + Set the script installation directory. If you don't supply this option + (via the command line or a configuration file), but you *have* supplied + an ``--install-dir`` (via command line or config file), then this option + defaults to the same directory, so that the scripts will be able to find + their associated package installation. Otherwise, this setting defaults + to the location where the distutils would normally install scripts, taking + any distutils configuration file settings into account. + +``--exclude-scripts, -x`` + Don't install scripts. This is useful if you need to install multiple + versions of a package, but do not want to reset the version that will be + run by scripts that are already installed. + +``--user`` (New in 0.6.11) + Use the user-site-packages as specified in :pep:`370` + instead of the global site-packages. + +``--always-copy, -a`` (New in 0.5a4) + Copy all needed distributions to the installation directory, even if they + are already present in a directory on sys.path. In older versions of + EasyInstall, this was the default behavior, but now you must explicitly + request it. By default, EasyInstall will no longer copy such distributions + from other sys.path directories to the installation directory, unless you + explicitly gave the distribution's filename on the command line. + + Note that as of 0.6a10, using this option excludes "system" and + "development" eggs from consideration because they can't be reliably + copied. This may cause EasyInstall to choose an older version of a package + than what you expected, or it may cause downloading and installation of a + fresh copy of something that's already installed. You will see warning + messages for any eggs that EasyInstall skips, before it falls back to an + older version or attempts to download a fresh copy. + +``--find-links=URLS_OR_FILENAMES, -f URLS_OR_FILENAMES`` + Scan the specified "download pages" or directories for direct links to eggs + or other distributions. Any existing file or directory names or direct + download URLs are immediately added to EasyInstall's search cache, and any + indirect URLs (ones that don't point to eggs or other recognized archive + formats) are added to a list of additional places to search for download + links. As soon as EasyInstall has to go online to find a package (either + because it doesn't exist locally, or because ``--upgrade`` or ``-U`` was + used), the specified URLs will be downloaded and scanned for additional + direct links. + + Eggs and archives found by way of ``--find-links`` are only downloaded if + they are needed to meet a requirement specified on the command line; links + to unneeded packages are ignored. + + If all requested packages can be found using links on the specified + download pages, the Python Package Index will not be consulted unless you + also specified the ``--upgrade`` or ``-U`` option. + + (Note: if you want to refer to a local HTML file containing links, you must + use a ``file:`` URL, as filenames that do not refer to a directory, egg, or + archive are ignored.) + + You may specify multiple URLs or file/directory names with this option, + separated by whitespace. Note that on the command line, you will probably + have to surround the URL list with quotes, so that it is recognized as a + single option value. You can also specify URLs in a configuration file; + see `Configuration Files`_, above. + + Changed in 0.6a10: previously all URLs and directories passed to this + option were scanned as early as possible, but from 0.6a10 on, only + directories and direct archive links are scanned immediately; URLs are not + retrieved unless a package search was already going to go online due to a + package not being available locally, or due to the use of the ``--update`` + or ``-U`` option. + +``--no-find-links`` Blocks the addition of any link. + This parameter is useful if you want to avoid adding links defined in a + project easy_install is installing (whether it's a requested project or a + dependency). When used, ``--find-links`` is ignored. + + Added in Distribute 0.6.11 and Setuptools 0.7. + +``--index-url=URL, -i URL`` (New in 0.4a1; default changed in 0.6c7) + Specifies the base URL of the Python Package Index. The default is + https://pypi.org/simple/ if not specified. When a package is requested + that is not locally available or linked from a ``--find-links`` download + page, the package index will be searched for download pages for the needed + package, and those download pages will be searched for links to download + an egg or source distribution. + +``--editable, -e`` (New in 0.6a1) + Only find and download source distributions for the specified projects, + unpacking them to subdirectories of the specified ``--build-directory``. + EasyInstall will not actually build or install the requested projects or + their dependencies; it will just find and extract them for you. See + `Editing and Viewing Source Packages`_ above for more details. + +``--build-directory=DIR, -b DIR`` (UPDATED in 0.6a1) + Set the directory used to build source packages. If a package is built + from a source distribution or checkout, it will be extracted to a + subdirectory of the specified directory. The subdirectory will have the + same name as the extracted distribution's project, but in all-lowercase. + If a file or directory of that name already exists in the given directory, + a warning will be printed to the console, and the build will take place in + a temporary directory instead. + + This option is most useful in combination with the ``--editable`` option, + which forces EasyInstall to *only* find and extract (but not build and + install) source distributions. See `Editing and Viewing Source Packages`_, + above, for more information. + +``--verbose, -v, --quiet, -q`` (New in 0.4a4) + Control the level of detail of EasyInstall's progress messages. The + default detail level is "info", which prints information only about + relatively time-consuming operations like running a setup script, unpacking + an archive, or retrieving a URL. Using ``-q`` or ``--quiet`` drops the + detail level to "warn", which will only display installation reports, + warnings, and errors. Using ``-v`` or ``--verbose`` increases the detail + level to include individual file-level operations, link analysis messages, + and distutils messages from any setup scripts that get run. If you include + the ``-v`` option more than once, the second and subsequent uses are passed + down to any setup scripts, increasing the verbosity of their reporting as + well. + +``--dry-run, -n`` (New in 0.4a4) + Don't actually install the package or scripts. This option is passed down + to any setup scripts run, so packages should not actually build either. + This does *not* skip downloading, nor does it skip extracting source + distributions to a temporary/build directory. + +``--optimize=LEVEL``, ``-O LEVEL`` (New in 0.4a4) + If you are installing from a source distribution, and are *not* using the + ``--zip-ok`` option, this option controls the optimization level for + compiling installed ``.py`` files to ``.pyo`` files. It does not affect + the compilation of modules contained in ``.egg`` files, only those in + ``.egg`` directories. The optimization level can be set to 0, 1, or 2; + the default is 0 (unless it's set under ``install`` or ``install_lib`` in + one of your distutils configuration files). + +``--record=FILENAME`` (New in 0.5a4) + Write a record of all installed files to FILENAME. This is basically the + same as the same option for the standard distutils "install" command, and + is included for compatibility with tools that expect to pass this option + to "setup.py install". + +``--site-dirs=DIRLIST, -S DIRLIST`` (New in 0.6a1) + Specify one or more custom "site" directories (separated by commas). + "Site" directories are directories where ``.pth`` files are processed, such + as the main Python ``site-packages`` directory. As of 0.6a10, EasyInstall + automatically detects whether a given directory processes ``.pth`` files + (or can be made to do so), so you should not normally need to use this + option. It is is now only necessary if you want to override EasyInstall's + judgment and force an installation directory to be treated as if it + supported ``.pth`` files. + +``--no-deps, -N`` (New in 0.6a6) + Don't install any dependencies. This is intended as a convenience for + tools that wrap eggs in a platform-specific packaging system. (We don't + recommend that you use it for anything else.) + +``--allow-hosts=PATTERNS, -H PATTERNS`` (New in 0.6a6) + Restrict downloading and spidering to hosts matching the specified glob + patterns. E.g. ``-H *.python.org`` restricts web access so that only + packages listed and downloadable from machines in the ``python.org`` + domain. The glob patterns must match the *entire* user/host/port section of + the target URL(s). For example, ``*.python.org`` will NOT accept a URL + like ``http://python.org/foo`` or ``http://www.python.org:8080/``. + Multiple patterns can be specified by separating them with commas. The + default pattern is ``*``, which matches anything. + + In general, this option is mainly useful for blocking EasyInstall's web + access altogether (e.g. ``-Hlocalhost``), or to restrict it to an intranet + or other trusted site. EasyInstall will do the best it can to satisfy + dependencies given your host restrictions, but of course can fail if it + can't find suitable packages. EasyInstall displays all blocked URLs, so + that you can adjust your ``--allow-hosts`` setting if it is more strict + than you intended. Some sites may wish to define a restrictive default + setting for this option in their `configuration files`_, and then manually + override the setting on the command line as needed. + +``--prefix=DIR`` (New in 0.6a10) + Use the specified directory as a base for computing the default + installation and script directories. On Windows, the resulting default + directories will be ``prefix\\Lib\\site-packages`` and ``prefix\\Scripts``, + while on other platforms the defaults will be + ``prefix/lib/python2.X/site-packages`` (with the appropriate version + substituted) for libraries and ``prefix/bin`` for scripts. + + Note that the ``--prefix`` option only sets the *default* installation and + script directories, and does not override the ones set on the command line + or in a configuration file. + +``--local-snapshots-ok, -l`` (New in 0.6c6) + Normally, EasyInstall prefers to only install *released* versions of + projects, not in-development ones, because such projects may not + have a currently-valid version number. So, it usually only installs them + when their ``setup.py`` directory is explicitly passed on the command line. + + However, if this option is used, then any in-development projects that were + installed using the ``setup.py develop`` command, will be used to build + eggs, effectively upgrading the "in-development" project to a snapshot + release. Normally, this option is used only in conjunction with the + ``--always-copy`` option to create a distributable snapshot of every egg + needed to run an application. + + Note that if you use this option, you must make sure that there is a valid + version number (such as an SVN revision number tag) for any in-development + projects that may be used, as otherwise EasyInstall may not be able to tell + what version of the project is "newer" when future installations or + upgrades are attempted. + + +.. _non-root installation: + +Custom Installation Locations +----------------------------- + +By default, EasyInstall installs python packages into Python's main ``site-packages`` directory, +and manages them using a custom ``.pth`` file in that same directory. + +Very often though, a user or developer wants ``easy_install`` to install and manage python packages +in an alternative location, usually for one of 3 reasons: + +1. They don't have access to write to the main Python site-packages directory. + +2. They want a user-specific stash of packages, that is not visible to other users. + +3. They want to isolate a set of packages to a specific python application, usually to minimize + the possibility of version conflicts. + +Historically, there have been many approaches to achieve custom installation. +The following section lists only the easiest and most relevant approaches [1]_. + +`Use the "--user" option`_ + +`Use the "--user" option and customize "PYTHONUSERBASE"`_ + +`Use "virtualenv"`_ + +.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_. + +.. _PEP-370: http://www.python.org/dev/peps/pep-0370/ + + +Use the "--user" option +~~~~~~~~~~~~~~~~~~~~~~~ +Python provides a User scheme for installation, which means that all +python distributions support an alternative install location that is specific to a user [3]_. +The Default location for each OS is explained in the python documentation +for the ``site.USER_BASE`` variable. This mode of installation can be turned on by +specifying the ``--user`` option to ``setup.py install`` or ``easy_install``. +This approach serves the need to have a user-specific stash of packages. + +.. [3] Prior to the User scheme, there was the Home scheme, which is still available, but requires more effort than the User scheme to get packages recognized. + +Use the "--user" option and customize "PYTHONUSERBASE" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The User scheme install location can be customized by setting the ``PYTHONUSERBASE`` environment +variable, which updates the value of ``site.USER_BASE``. To isolate packages to a specific +application, simply set the OS environment of that application to a specific value of +``PYTHONUSERBASE``, that contains just those packages. + +Use "virtualenv" +~~~~~~~~~~~~~~~~ +"virtualenv" is a 3rd-party python package that effectively "clones" a python installation, thereby +creating an isolated location to install packages. The evolution of "virtualenv" started before the existence +of the User installation scheme. "virtualenv" provides a version of ``easy_install`` that is +scoped to the cloned python install and is used in the normal way. "virtualenv" does offer various features +that the User installation scheme alone does not provide, e.g. the ability to hide the main python site-packages. + +Please refer to the `virtualenv`_ documentation for more details. + +.. _virtualenv: https://pypi.org/project/virtualenv/ + + + +Package Index "API" +------------------- + +Custom package indexes (and PyPI) must follow the following rules for +EasyInstall to be able to look up and download packages: + +1. Except where stated otherwise, "pages" are HTML or XHTML, and "links" + refer to ``href`` attributes. + +2. Individual project version pages' URLs must be of the form + ``base/projectname/version``, where ``base`` is the package index's base URL. + +3. Omitting the ``/version`` part of a project page's URL (but keeping the + trailing ``/``) should result in a page that is either: + + a) The single active version of that project, as though the version had been + explicitly included, OR + + b) A page with links to all of the active version pages for that project. + +4. Individual project version pages should contain direct links to downloadable + distributions where possible. It is explicitly permitted for a project's + "long_description" to include URLs, and these should be formatted as HTML + links by the package index, as EasyInstall does no special processing to + identify what parts of a page are index-specific and which are part of the + project's supplied description. + +5. Where available, MD5 information should be added to download URLs by + appending a fragment identifier of the form ``#md5=...``, where ``...`` is + the 32-character hex MD5 digest. EasyInstall will verify that the + downloaded file's MD5 digest matches the given value. + +6. Individual project version pages should identify any "homepage" or + "download" URLs using ``rel="homepage"`` and ``rel="download"`` attributes + on the HTML elements linking to those URLs. Use of these attributes will + cause EasyInstall to always follow the provided links, unless it can be + determined by inspection that they are downloadable distributions. If the + links are not to downloadable distributions, they are retrieved, and if they + are HTML, they are scanned for download links. They are *not* scanned for + additional "homepage" or "download" links, as these are only processed for + pages that are part of a package index site. + +7. The root URL of the index, if retrieved with a trailing ``/``, must result + in a page containing links to *all* projects' active version pages. + + (Note: This requirement is a workaround for the absence of case-insensitive + ``safe_name()`` matching of project names in URL paths. If project names are + matched in this fashion (e.g. via the PyPI server, mod_rewrite, or a similar + mechanism), then it is not necessary to include this all-packages listing + page.) + +8. If a package index is accessed via a ``file://`` URL, then EasyInstall will + automatically use ``index.html`` files, if present, when trying to read a + directory with a trailing ``/`` on the URL. diff --git a/docs/index.txt b/docs/index.txt index c251260d..13a46e74 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,4 +21,5 @@ Documentation content: python3 development roadmap + Deprecated: Easy Install history diff --git a/easy_install.py b/easy_install.py new file mode 100644 index 00000000..d87e9840 --- /dev/null +++ b/easy_install.py @@ -0,0 +1,5 @@ +"""Run the EasyInstall command""" + +if __name__ == '__main__': + from setuptools.command.easy_install import main + main() diff --git a/setup.cfg b/setup.cfg index 385ba14d..42a3d86c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ classifiers = [options] zip_safe = True python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* +py_modules = easy_install packages = find: [options.packages.find] diff --git a/setup.py b/setup.py index 59efc237..d97895fc 100755 --- a/setup.py +++ b/setup.py @@ -31,6 +31,22 @@ def read_commands(): return command_ns['__all__'] +def _gen_console_scripts(): + yield "easy_install = setuptools.command.easy_install:main" + + # Gentoo distributions manage the python-version-specific scripts + # themselves, so those platforms define an environment variable to + # suppress the creation of the version-specific scripts. + var_names = ( + 'SETUPTOOLS_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', + 'DISTRIBUTE_DISABLE_VERSIONED_EASY_INSTALL_SCRIPT', + ) + if any(os.environ.get(var) not in (None, "", "0") for var in var_names): + return + tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main" + yield tmpl.format(shortver='{}.{}'.format(*sys.version_info)) + + package_data = dict( setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'], ) @@ -109,6 +125,9 @@ setup_params = dict( "depends.txt = setuptools.command.egg_info:warn_depends_obsolete", "dependency_links.txt = setuptools.command.egg_info:overwrite_arg", ], + "console_scripts": list(_gen_console_scripts()), + "setuptools.installation": + ['eggsecutable = setuptools.command.easy_install:bootstrap'], }, dependency_links=[ pypi_link( diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index d273bc10..09066f8c 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -73,7 +73,7 @@ warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) __all__ = [ 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', - 'get_exe_prefixes', + 'main', 'get_exe_prefixes', ] @@ -2289,6 +2289,59 @@ def current_umask(): return tmp +def bootstrap(): + # This function is called when setuptools*.egg is run using /bin/sh + import setuptools + + argv0 = os.path.dirname(setuptools.__path__[0]) + sys.argv[0] = argv0 + sys.argv.append(argv0) + main() + + +def main(argv=None, **kw): + from setuptools import setup + from setuptools.dist import Distribution + + class DistributionWithoutHelpCommands(Distribution): + common_usage = "" + + def _show_help(self, *args, **kw): + with _patch_usage(): + Distribution._show_help(self, *args, **kw) + + if argv is None: + argv = sys.argv[1:] + + with _patch_usage(): + setup( + script_args=['-q', 'easy_install', '-v'] + argv, + script_name=sys.argv[0] or 'easy_install', + distclass=DistributionWithoutHelpCommands, + **kw + ) + + +@contextlib.contextmanager +def _patch_usage(): + import distutils.core + USAGE = textwrap.dedent(""" + usage: %(script)s [options] requirement_or_url ... + or: %(script)s --help + """).lstrip() + + def gen_usage(script_name): + return USAGE % dict( + script=os.path.basename(script_name), + ) + + saved = distutils.core.gen_usage + distutils.core.gen_usage = gen_usage + try: + yield + finally: + distutils.core.gen_usage = saved + class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 68319c2f..aa75899a 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -467,24 +467,22 @@ class TestSetupRequires: """ monkeypatch.setenv(str('PIP_RETRIES'), str('0')) monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) - monkeypatch.setenv(str('PIP_VERBOSE'), str('1')) - # create an sdist that has a build-time dependency. - with TestSetupRequires.create_sdist() as dist_file: - with contexts.tempdir() as temp_dir: - setup_py = os.path.join(temp_dir, 'setup.py') - with open(setup_py, 'w') as fp: - fp.write('__import__("setuptools").setup()') - temp_install_dir = os.path.join(temp_dir, 'target') - os.mkdir(temp_install_dir) - with contexts.environment(PYTHONPATH=temp_install_dir): - # attempt to install the dist. It should - # fail because it doesn't exist. - with pytest.raises(SystemExit): - run_setup(setup_py, ['easy_install', - '--exclude-scripts', - '--index-url', mock_index.url, - '--install-dir', temp_install_dir, - dist_file]) + with contexts.quiet(): + # create an sdist that has a build-time dependency. + with TestSetupRequires.create_sdist() as dist_file: + with contexts.tempdir() as temp_install_dir: + with contexts.environment(PYTHONPATH=temp_install_dir): + ei_params = [ + '--index-url', mock_index.url, + '--exclude-scripts', + '--install-dir', temp_install_dir, + dist_file, + ] + with sandbox.save_argv(['easy_install']): + # attempt to install the dist. It should + # fail because it doesn't exist. + with pytest.raises(SystemExit): + easy_install_pkg.main(ei_params) # there should have been one requests to the server assert [r.path for r in mock_index.requests] == ['/does-not-exist/'] diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py index 3c5df68a..f937d981 100644 --- a/setuptools/tests/test_namespaces.py +++ b/setuptools/tests/test_namespaces.py @@ -64,8 +64,9 @@ class TestNamespaces: target.mkdir() install_cmd = [ sys.executable, - '-m', 'pip.__main__', 'install', - '-t', str(target), str(pkg), + '-m', 'easy_install', + '-d', str(target), + str(pkg), ] with test.test.paths_on_pythonpath([str(target)]): subprocess.check_call(install_cmd) -- cgit v1.2.3 From 4188aba5265e9b7145b1c5ed10c8e0ae769f70b4 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Sun, 17 Nov 2019 22:24:05 +0100 Subject: add changelog entries --- changelog.d/1830.breaking.rst | 7 +++++++ changelog.d/1909.breaking.rst | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 changelog.d/1830.breaking.rst create mode 100644 changelog.d/1909.breaking.rst diff --git a/changelog.d/1830.breaking.rst b/changelog.d/1830.breaking.rst new file mode 100644 index 00000000..9f2214ab --- /dev/null +++ b/changelog.d/1830.breaking.rst @@ -0,0 +1,7 @@ +Mark the easy_install script and setuptools command as deprecated, and use `pip `_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior: + * support for ``python_requires`` + * better support for wheels (proper handling of priority with respect to PEP 425 tags) + * PEP 517/518 support + * eggs are not supported + * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored) + * pip environment variables are honored (and take precedence over easy_install options) diff --git a/changelog.d/1909.breaking.rst b/changelog.d/1909.breaking.rst new file mode 100644 index 00000000..9f2214ab --- /dev/null +++ b/changelog.d/1909.breaking.rst @@ -0,0 +1,7 @@ +Mark the easy_install script and setuptools command as deprecated, and use `pip `_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior: + * support for ``python_requires`` + * better support for wheels (proper handling of priority with respect to PEP 425 tags) + * PEP 517/518 support + * eggs are not supported + * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored) + * pip environment variables are honored (and take precedence over easy_install options) -- cgit v1.2.3 From d155aa0d61690b7013de968b912a001d18f5cfdd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 Nov 2019 14:43:07 -0500 Subject: =?UTF-8?q?Bump=20version:=2041.6.0=20=E2=86=92=2042.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 20 ++++++++++++++++++++ changelog.d/1767.change.rst | 2 -- changelog.d/1829.change.rst | 3 --- changelog.d/1830.breaking.rst | 7 ------- changelog.d/1861.change.rst | 1 - changelog.d/1877.change.rst | 1 - changelog.d/1898.breaking.rst | 1 - changelog.d/1909.breaking.rst | 7 ------- setup.cfg | 2 +- 10 files changed, 22 insertions(+), 24 deletions(-) delete mode 100644 changelog.d/1767.change.rst delete mode 100644 changelog.d/1829.change.rst delete mode 100644 changelog.d/1830.breaking.rst delete mode 100644 changelog.d/1861.change.rst delete mode 100644 changelog.d/1877.change.rst delete mode 100644 changelog.d/1898.breaking.rst delete mode 100644 changelog.d/1909.breaking.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 40db5b03..0551f1b0 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 41.6.0 +current_version = 42.0.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index ba7b4647..0a8696c2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,23 @@ +v42.0.0 +------- + +* #1830, #1909: Mark the easy_install script and setuptools command as deprecated, and use `pip `_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior: + * support for ``python_requires`` + * better support for wheels (proper handling of priority with respect to PEP 425 tags) + * PEP 517/518 support + * eggs are not supported + * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored) + * pip environment variables are honored (and take precedence over easy_install options) +* #1898: Removed the "upload" and "register" commands in favor of `twine `_. +* #1767: Add support for the ``license_files`` option in ``setup.cfg`` to automatically + include multiple license files in a source distribution. +* #1829: Update handling of wheels compatibility tags: + * add support for manylinux2010 + * fix use of removed 'm' ABI flag in Python 3.8 on Windows +* #1861: Fix empty namespace package installation from wheel. +* #1877: Setuptools now exposes a new entry point hook "setuptools.finalize_distribution_options", enabling plugins like `setuptools_scm `_ to configure options on the distribution at finalization time. + + v41.6.0 ------- diff --git a/changelog.d/1767.change.rst b/changelog.d/1767.change.rst deleted file mode 100644 index 5d42aedc..00000000 --- a/changelog.d/1767.change.rst +++ /dev/null @@ -1,2 +0,0 @@ -Add support for the ``license_files`` option in ``setup.cfg`` to automatically -include multiple license files in a source distribution. diff --git a/changelog.d/1829.change.rst b/changelog.d/1829.change.rst deleted file mode 100644 index 36be832a..00000000 --- a/changelog.d/1829.change.rst +++ /dev/null @@ -1,3 +0,0 @@ -Update handling of wheels compatibility tags: -* add support for manylinux2010 -* fix use of removed 'm' ABI flag in Python 3.8 on Windows diff --git a/changelog.d/1830.breaking.rst b/changelog.d/1830.breaking.rst deleted file mode 100644 index 9f2214ab..00000000 --- a/changelog.d/1830.breaking.rst +++ /dev/null @@ -1,7 +0,0 @@ -Mark the easy_install script and setuptools command as deprecated, and use `pip `_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior: - * support for ``python_requires`` - * better support for wheels (proper handling of priority with respect to PEP 425 tags) - * PEP 517/518 support - * eggs are not supported - * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored) - * pip environment variables are honored (and take precedence over easy_install options) diff --git a/changelog.d/1861.change.rst b/changelog.d/1861.change.rst deleted file mode 100644 index 5a4e0a56..00000000 --- a/changelog.d/1861.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix empty namespace package installation from wheel. diff --git a/changelog.d/1877.change.rst b/changelog.d/1877.change.rst deleted file mode 100644 index 5a744fa3..00000000 --- a/changelog.d/1877.change.rst +++ /dev/null @@ -1 +0,0 @@ -Setuptools now exposes a new entry point hook "setuptools.finalize_distribution_options", enabling plugins like `setuptools_scm `_ to configure options on the distribution at finalization time. diff --git a/changelog.d/1898.breaking.rst b/changelog.d/1898.breaking.rst deleted file mode 100644 index 844a8a42..00000000 --- a/changelog.d/1898.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -Removed the "upload" and "register" commands in favor of `twine `_. diff --git a/changelog.d/1909.breaking.rst b/changelog.d/1909.breaking.rst deleted file mode 100644 index 9f2214ab..00000000 --- a/changelog.d/1909.breaking.rst +++ /dev/null @@ -1,7 +0,0 @@ -Mark the easy_install script and setuptools command as deprecated, and use `pip `_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior: - * support for ``python_requires`` - * better support for wheels (proper handling of priority with respect to PEP 425 tags) - * PEP 517/518 support - * eggs are not supported - * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored) - * pip environment variables are honored (and take precedence over easy_install options) diff --git a/setup.cfg b/setup.cfg index 42a3d86c..c0aa35ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 41.6.0 +version = 42.0.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 926c80f5e84823f48103f3695f55f23949cc5d37 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 25 Nov 2019 11:24:10 +0100 Subject: wheel: fix `is_compatible` implementation --- changelog.d/1918.change.rst | 1 + setuptools/tests/test_wheel.py | 9 +++++++++ setuptools/wheel.py | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 changelog.d/1918.change.rst diff --git a/changelog.d/1918.change.rst b/changelog.d/1918.change.rst new file mode 100644 index 00000000..29d00456 --- /dev/null +++ b/changelog.d/1918.change.rst @@ -0,0 +1 @@ +Fix regression in handling wheels compatibility tags. diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index d50816c2..55d346c6 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -18,6 +18,7 @@ import pytest from pkg_resources import Distribution, PathMetadata, PY_MAJOR from setuptools.extern.packaging.utils import canonicalize_name +from setuptools.extern.packaging.tags import parse_tag from setuptools.wheel import Wheel from .contexts import tempdir @@ -571,3 +572,11 @@ def test_wheel_no_dist_dir(): _check_wheel_install(wheel_path, install_dir, None, project_name, version, None) + + +def test_wheel_is_compatible(monkeypatch): + def sys_tags(): + for t in parse_tag('cp36-cp36m-manylinux1_x86_64'): + yield t + monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags) + assert Wheel('onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible() diff --git a/setuptools/wheel.py b/setuptools/wheel.py index 3effd79b..025aaa82 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -77,7 +77,7 @@ class Wheel: def is_compatible(self): '''Is the wheel is compatible with the current platform?''' - supported_tags = set(map(str, 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) def egg_name(self): -- cgit v1.2.3 From e84f616a6507ec9115fad68b221cbf5333d9d2d9 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Mon, 25 Nov 2019 12:08:38 +0100 Subject: =?UTF-8?q?Bump=20version:=2042.0.0=20=E2=86=92=2042.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 6 ++++++ changelog.d/1918.change.rst | 1 - setup.cfg | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/1918.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0551f1b0..e37acce5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 42.0.0 +current_version = 42.0.1 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 0a8696c2..da657c28 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v42.0.1 +------- + +* #1918: Fix regression in handling wheels compatibility tags. + + v42.0.0 ------- diff --git a/changelog.d/1918.change.rst b/changelog.d/1918.change.rst deleted file mode 100644 index 29d00456..00000000 --- a/changelog.d/1918.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix regression in handling wheels compatibility tags. diff --git a/setup.cfg b/setup.cfg index c0aa35ba..b8e54279 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 42.0.0 +version = 42.0.1 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 6f46a4b703d4db225e96bb871e1bf6a7c3597329 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 26 Nov 2019 18:46:34 +0100 Subject: fix support for easy_install's find-links option in setup.cfg --- changelog.d/1921.change.txt | 1 + setuptools/installer.py | 13 ++++++++++-- setuptools/tests/test_easy_install.py | 38 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1921.change.txt diff --git a/changelog.d/1921.change.txt b/changelog.d/1921.change.txt new file mode 100644 index 00000000..7c001eb8 --- /dev/null +++ b/changelog.d/1921.change.txt @@ -0,0 +1 @@ +Fix support for easy_install's ``find-links`` option in ``setup.cfg``. diff --git a/setuptools/installer.py b/setuptools/installer.py index 35bc3cc5..a5816608 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -7,11 +7,20 @@ from distutils.errors import DistutilsError import pkg_resources from setuptools.command.easy_install import easy_install +from setuptools.extern import six from setuptools.wheel import Wheel from .py31compat import TemporaryDirectory +def _fixup_find_links(find_links): + """Ensure find-links option end-up being a list of strings.""" + if isinstance(find_links, six.string_types): + return find_links.split() + assert isinstance(find_links, (tuple, list)) + return find_links + + def _legacy_fetch_build_egg(dist, req): """Fetch an egg needed for building. @@ -31,7 +40,7 @@ def _legacy_fetch_build_egg(dist, req): if dist.dependency_links: links = dist.dependency_links[:] if 'find_links' in opts: - links = opts['find_links'][1] + links + links = _fixup_find_links(opts['find_links'][1]) + links opts['find_links'] = ('setup', links) install_dir = dist.get_egg_cache_dir() cmd = easy_install( @@ -84,7 +93,7 @@ def fetch_build_egg(dist, req): else: index_url = None if 'find_links' in opts: - find_links = opts['find_links'][1][:] + find_links = _fixup_find_links(opts['find_links'][1])[:] else: find_links = [] if dist.dependency_links: diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index aa75899a..a21651ec 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -744,6 +744,44 @@ class TestSetupRequires: eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) assert eggs == ['dep 1.0'] + @pytest.mark.parametrize('use_legacy_installer,with_dependency_links_in_setup_py', + itertools.product((False, True), (False, True))) + def test_setup_requires_with_find_links_in_setup_cfg(self, monkeypatch, + use_legacy_installer, + with_dependency_links_in_setup_py): + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + make_trivial_sdist(os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42') + test_pkg = os.path.join(temp_dir, 'test_pkg') + test_setup_py = os.path.join(test_pkg, 'setup.py') + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + os.mkdir(test_pkg) + with open(test_setup_py, 'w') as fp: + if with_dependency_links_in_setup_py: + dependency_links = [os.path.join(temp_dir, 'links')] + else: + dependency_links = [] + fp.write(DALS( + ''' + from setuptools import installer, setup + if {use_legacy_installer}: + installer.fetch_build_egg = installer._legacy_fetch_build_egg + setup(setup_requires='python-xlib==42', + dependency_links={dependency_links!r}) + ''').format(use_legacy_installer=use_legacy_installer, + dependency_links=dependency_links)) + with open(test_setup_cfg, 'w') as fp: + fp.write(DALS( + ''' + [easy_install] + index_url = {index_url} + find_links = {find_links} + ''').format(index_url=os.path.join(temp_dir, 'index'), + find_links=temp_dir)) + run_setup(test_setup_py, [str('--version')]) + def make_trivial_sdist(dist_path, distname, version): """ -- cgit v1.2.3 From 7502dc9ca767927db9599f93cd48851ca59f7a62 Mon Sep 17 00:00:00 2001 From: Benoit Pierre Date: Tue, 26 Nov 2019 20:56:57 +0100 Subject: fix possible issue with transitive build dependencies Handle the case where a missing transitive build dependency is required by an extra for an already installed build dependency. --- changelog.d/1922.change.rst | 1 + setuptools/installer.py | 7 ++++-- setuptools/tests/test_easy_install.py | 44 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1922.change.rst diff --git a/changelog.d/1922.change.rst b/changelog.d/1922.change.rst new file mode 100644 index 00000000..7aeb251c --- /dev/null +++ b/changelog.d/1922.change.rst @@ -0,0 +1 @@ +Fix possible issue with transitive build dependencies. diff --git a/setuptools/installer.py b/setuptools/installer.py index 35bc3cc5..ba9cfce9 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -64,8 +64,11 @@ def fetch_build_egg(dist, req): pkg_resources.get_distribution('wheel') except pkg_resources.DistributionNotFound: dist.announce('WARNING: The wheel package is not available.', log.WARN) - if not isinstance(req, pkg_resources.Requirement): - req = pkg_resources.Requirement.parse(req) + # Ignore environment markers: if we're here, it's needed. This ensure + # we don't try to ask pip for something like `babel; extra == "i18n"`, + # which would always be ignored. + req = pkg_resources.Requirement.parse(str(req)) + req.marker = None # Take easy_install options into account, but do not override relevant # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll # take precedence. diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index aa75899a..f6da1b16 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -37,6 +37,7 @@ from setuptools.tests import fail_on_ascii import pkg_resources from . import contexts +from .files import build_files from .textwrap import DALS __metaclass__ = type @@ -744,6 +745,49 @@ class TestSetupRequires: eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) assert eggs == ['dep 1.0'] + def test_setup_requires_with_transitive_extra_dependency(self, monkeypatch): + # Use case: installing a package with a build dependency on + # an already installed `dep[extra]`, which in turn depends + # on `extra_dep` (whose is not already installed). + with contexts.save_pkg_resources_state(): + with contexts.tempdir() as temp_dir: + # Create source distribution for `extra_dep`. + make_trivial_sdist(os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0') + # Create source tree for `dep`. + dep_pkg = os.path.join(temp_dir, 'dep') + os.mkdir(dep_pkg) + build_files({ + 'setup.py': + DALS(""" + import setuptools + setuptools.setup( + name='dep', version='2.0', + extras_require={'extra': ['extra_dep']}, + ) + """), + 'setup.cfg': '', + }, prefix=dep_pkg) + # "Install" dep. + run_setup(os.path.join(dep_pkg, 'setup.py'), [str('dist_info')]) + working_set.add_entry(dep_pkg) + # Create source tree for test package. + test_pkg = os.path.join(temp_dir, 'test_pkg') + test_setup_py = os.path.join(test_pkg, 'setup.py') + test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') + os.mkdir(test_pkg) + with open(test_setup_py, 'w') as fp: + fp.write(DALS( + ''' + from setuptools import installer, setup + setup(setup_requires='dep[extra]') + ''')) + # Check... + monkeypatch.setenv(str('PIP_FIND_LINKS'), str(temp_dir)) + monkeypatch.setenv(str('PIP_NO_INDEX'), str('1')) + monkeypatch.setenv(str('PIP_RETRIES'), str('0')) + monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) + run_setup(test_setup_py, [str('--version')]) + def make_trivial_sdist(dist_path, distname, version): """ -- cgit v1.2.3 From cbd977b8252f1df53aca7f09cf6160590b3b2ed0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 20:42:33 -0500 Subject: Rename 'Professional support' to 'For Enterprise' and add section on 'For Enterprise' to the README (linking to Tidelift). --- README.rst | 11 ++++++++++- docs/_templates/indexsidebar.html | 14 +++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index dac8a468..da0549a9 100644 --- a/README.rst +++ b/README.rst @@ -34,8 +34,17 @@ To report a security vulnerability, please use the Tidelift will coordinate the fix and disclosure. +For Enterprise +============== + +Available as part of the Tidelift Subscription. + +Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. + +`Learn more `_. + Code of Conduct ---------------- +=============== Everyone interacting in the setuptools project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html index 504de6b0..d803b8a3 100644 --- a/docs/_templates/indexsidebar.html +++ b/docs/_templates/indexsidebar.html @@ -1,3 +1,10 @@ +

For Enterprise

+ +

+Professionally-supported {{ project }} is available with the +Tidelift Subscription. +

+

Download

Current version: {{ version }}

@@ -6,10 +13,3 @@

Questions? Suggestions? Contributions?

Visit the Project page

- -

Professional support

- -

-Professionally-supported {{ project }} is available with the -Tidelift Subscription. -

-- cgit v1.2.3 From 769658cc0fb73347d044de0279dc0a361c04b316 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 21:05:15 -0500 Subject: Don't pin to old Python as 'default' --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7088d166..f37529d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,7 @@ jobs: env: DISABLE_COVERAGE=1 - python: 3.4 - python: 3.5 - - &default_py - python: 3.6 + - python: 3.6 - python: 3.7 - &latest_py3 python: 3.8 @@ -24,7 +23,7 @@ jobs: - python: 3.8-dev - <<: *latest_py3 env: TOXENV=docs DISABLE_COVERAGE=1 - - <<: *default_py + - <<: *latest_py3 stage: deploy (to PyPI for tagged commits) if: tag IS present install: skip -- cgit v1.2.3 From d7bdf132857be9da15a6c3b733f97e998725cdba Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 21:07:53 -0500 Subject: Add 'release' tox environment from jaraco/skeleton --- tox.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tox.ini b/tox.ini index 5d439cb3..faccffdc 100644 --- a/tox.ini +++ b/tox.ini @@ -55,3 +55,18 @@ source= setuptools omit= */_vendor/* + +[testenv:release] +skip_install = True +deps = + pep517>=0.5 + twine[keyring]>=1.13 + path +passenv = + TWINE_PASSWORD +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} +commands = + python -c "import path; path.Path('dist').rmtree_p()" + python -m pep517.build . + python -m twine upload dist/* -- cgit v1.2.3 From ef3c044ca799e8796a872170adc09796ef59f7da Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 21:17:01 -0500 Subject: Invoke bootstrap prior to cutting release. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index faccffdc..a82b902b 100644 --- a/tox.ini +++ b/tox.ini @@ -67,6 +67,7 @@ passenv = setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = + python -m bootstrap python -c "import path; path.Path('dist').rmtree_p()" python -m pep517.build . python -m twine upload dist/* -- cgit v1.2.3 From 350a74162aa4b4893d2d50a4455304755e2014be Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 21:24:24 -0500 Subject: Simply invoke the tox 'release' environment to cut releases. --- .travis.yml | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index f37529d9..bcff8ad9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,25 +24,11 @@ jobs: - <<: *latest_py3 env: TOXENV=docs DISABLE_COVERAGE=1 - <<: *latest_py3 - stage: deploy (to PyPI for tagged commits) + stage: deploy if: tag IS present install: skip - script: skip - after_success: true - before_deploy: - - python bootstrap.py - - "! grep pyc setuptools.egg-info/SOURCES.txt" - deploy: - provider: pypi - on: - tags: true - all_branches: true - user: __token__ - password: - secure: FSp9KU+pdvWPxBOaxe6BNmcJ9y8259G3/NdTJ00r0qx/xMLpSneGjpuLqoD6BL2JoM6gRwurwakWoH/9Ah+Di7afETjMnL6WJKtDZ+Uu3YLx3ss7/FlhVz6zmVTaDJUzuo9dGr//qLBQTIxVjGYfQelRJyfMAXtrYWdeT/4489E45lMw+86Z/vnSBOxs4lWekeQW5Gem0cDViWu67RRiGkAEvrYVwuImMr2Dyhpv+l/mQGQIS/ezXuAEFToE6+q8VUVe/aK498Qovdc+O4M7OYk1JouFpffZ3tVZ6iWHQFcR11480UdI6VCIcFpPvGC/J8MWUWLjq7YOm0X9jPXgdYMUQLAP4clFgUr2qNoRSKWfuQlNdVVuS2htYcjJ3eEl90FhcIZKp+WVMrypRPOQJ8CBielZEs0dhytRrZSaJC1BNq25O/BPzws8dL8hYtoXsM6I3Zv5cZgdyqyq/eOEMCX7Cetv6do0U41VGEV5UohvyyuwH5l9GCuPREpY3sXayPg8fw7XcPjvvzSVyjcUT/ePW8sfnAyWZnngjweAn6dK8IFGPuSPQdlos78uxeUOvCVUW0xv/0m4lX73yoHdVVdLbu1MJTyibFGec86Bew9JqIcDlhHaIJ9ihZ9Z9tOtvp1cuNyKYE4kvmOtumDDicEw4DseYn2z5sZDTYTBsKY= - distributions: release - skip_cleanup: true - skip_upload_docs: true + script: tox -e release + after_success: skip cache: pip -- cgit v1.2.3 From 6429e2c54ba8d6cbb2d8d8e7108b91122cb7039a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 08:55:53 -0500 Subject: Restore 'setup.py' release step --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index a82b902b..21ec6cde 100644 --- a/tox.ini +++ b/tox.ini @@ -59,7 +59,7 @@ omit= [testenv:release] skip_install = True deps = - pep517>=0.5 + wheel twine[keyring]>=1.13 path passenv = @@ -69,5 +69,5 @@ setenv = commands = python -m bootstrap python -c "import path; path.Path('dist').rmtree_p()" - python -m pep517.build . + python setup.py release python -m twine upload dist/* -- cgit v1.2.3 From 7a709b6d30dac9409707b1b9bf50cd7022e35118 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 09:14:49 -0500 Subject: Restore build-backend and remove switch to avoid pep517. Ref #1644. --- pyproject.toml | 3 ++- tools/tox_pip.py | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 07c23bb5..5a2d7d3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] -requires = ["wheel"] +requires = ["setuptools >= 40.8", "wheel"] +build-backend = "setuptools.build_meta" [tool.towncrier] package = "setuptools" diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 5aeca805..63518f92 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -21,12 +21,6 @@ def pip(args): pypath = pypath.split(os.pathsep) if pypath is not None else [] pypath.insert(0, TOX_PIP_DIR) os.environ['PYTHONPATH'] = os.pathsep.join(pypath) - # Disable PEP 517 support when using editable installs. - for n, a in enumerate(args): - if not a.startswith('-'): - if a in 'install' and '-e' in args[n:]: - args.insert(n + 1, '--no-use-pep517') - break # Fix call for setuptools editable install. for n, a in enumerate(args): if a == '.': -- cgit v1.2.3 From 2292718a151994efb7d364312c73d5b536988049 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 09:23:31 -0500 Subject: Reword changelog to give more context --- changelog.d/1922.change.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/1922.change.rst b/changelog.d/1922.change.rst index 7aeb251c..837ef9c9 100644 --- a/changelog.d/1922.change.rst +++ b/changelog.d/1922.change.rst @@ -1 +1 @@ -Fix possible issue with transitive build dependencies. +Build dependencies (setup_requires and tests_require) now install transitive dependencies indicated by extras. -- cgit v1.2.3 From a2e883e1b838db529d992d4c6c8ab73c16f48591 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 09:38:13 -0500 Subject: Extract function to strip the marker for concise code in the long function. --- setuptools/installer.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/setuptools/installer.py b/setuptools/installer.py index ba9cfce9..527b95de 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -64,11 +64,8 @@ def fetch_build_egg(dist, req): pkg_resources.get_distribution('wheel') except pkg_resources.DistributionNotFound: dist.announce('WARNING: The wheel package is not available.', log.WARN) - # Ignore environment markers: if we're here, it's needed. This ensure - # we don't try to ask pip for something like `babel; extra == "i18n"`, - # which would always be ignored. - req = pkg_resources.Requirement.parse(str(req)) - req.marker = None + # Ignore environment markers; if supplied, it is required. + req = strip_marker(req) # Take easy_install options into account, but do not override relevant # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll # take precedence. @@ -130,3 +127,15 @@ def fetch_build_egg(dist, req): dist = pkg_resources.Distribution.from_filename( dist_location, metadata=dist_metadata) return dist + + +def strip_marker(req): + """ + Return a new requirement without the environment marker to avoid + calling pip with something like `babel; extra == "i18n"`, which + would always be ignored. + """ + # create a copy to avoid mutating the input + req = pkg_resources.Requirement.parse(str(req)) + req.marker = None + return req -- cgit v1.2.3 From 53b2eb605de63b1c7589696ad55780b6ae0b7dcf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 09:51:09 -0500 Subject: Publish release notes to tidelift following release. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 21ec6cde..6d3b9a9b 100644 --- a/tox.ini +++ b/tox.ini @@ -62,8 +62,10 @@ deps = wheel twine[keyring]>=1.13 path + jaraco.tidelift passenv = TWINE_PASSWORD + TIDELIFT_TOKEN setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = @@ -71,3 +73,4 @@ commands = python -c "import path; path.Path('dist').rmtree_p()" python setup.py release python -m twine upload dist/* + python -m jaraco.tidelift.publish-release-notes -- cgit v1.2.3 From 6fa879b961c6623750a8a25325eeeda9e68fa541 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 09:52:47 -0500 Subject: =?UTF-8?q?Bump=20version:=2042.0.1=20=E2=86=92=2042.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 7 +++++++ changelog.d/1921.change.txt | 1 - changelog.d/1922.change.rst | 1 - setup.cfg | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/1921.change.txt delete mode 100644 changelog.d/1922.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e37acce5..8a9f4435 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 42.0.1 +current_version = 42.0.2 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index da657c28..81abbe59 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v42.0.2 +------- + +* #1921: Fix support for easy_install's ``find-links`` option in ``setup.cfg``. +* #1922: Build dependencies (setup_requires and tests_require) now install transitive dependencies indicated by extras. + + v42.0.1 ------- diff --git a/changelog.d/1921.change.txt b/changelog.d/1921.change.txt deleted file mode 100644 index 7c001eb8..00000000 --- a/changelog.d/1921.change.txt +++ /dev/null @@ -1 +0,0 @@ -Fix support for easy_install's ``find-links`` option in ``setup.cfg``. diff --git a/changelog.d/1922.change.rst b/changelog.d/1922.change.rst deleted file mode 100644 index 837ef9c9..00000000 --- a/changelog.d/1922.change.rst +++ /dev/null @@ -1 +0,0 @@ -Build dependencies (setup_requires and tests_require) now install transitive dependencies indicated by extras. diff --git a/setup.cfg b/setup.cfg index b8e54279..68b49d73 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 42.0.1 +version = 42.0.2 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 1d03fdc94c3676a5b675ec7d818d48c6a772fb49 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 1 Dec 2019 10:04:20 -0500 Subject: Ensure tox is present for cutting release. Ref #1925. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bcff8ad9..b3a6556d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,6 @@ jobs: - <<: *latest_py3 stage: deploy if: tag IS present - install: skip script: tox -e release after_success: skip -- cgit v1.2.3 From 0fc2a2acd6cc64b37b67e5f42e4d15d8e734c01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Thu, 12 Dec 2019 13:55:07 +0100 Subject: Update setuptools.txt --- docs/setuptools.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index c109e673..03b57cf3 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -62,7 +62,7 @@ Installing ``setuptools`` To install the latest version of setuptools, use:: - pip install -U setuptools + pip install --upgrade setuptools Refer to `Installing Packages`_ guide for more information. @@ -1199,7 +1199,7 @@ command; see the section on the `develop`_ command below for more details. Note that you can also apply setuptools commands to non-setuptools projects, using commands like this:: - python -c "import setuptools; execfile('setup.py')" develop + python -c "import setuptools; with open('setup.py') as f: exec(compile(f.read(), 'setup.py', 'exec'))" develop That is, you can simply list the normal setup commands and options following the quoted part. @@ -1215,7 +1215,7 @@ Detailed instructions to distribute a setuptools project can be found at Before you begin, make sure you have the latest versions of setuptools and wheel:: - python3 -m pip install --user --upgrade setuptools wheel + pip install --upgrade setuptools wheel To build a setuptools project, run this command from the same directory where setup.py is located:: @@ -1229,15 +1229,15 @@ https://test.pypi.org/account/register/. You will also need to verify your email to be able to upload any packages. You should install twine to be able to upload packages:: - python3 -m pip install --user --upgrade setuptools wheel + pip install --upgrade twine Now, to upload these archives, run:: - twine upload --repository-url https://test.pypi.org/legacy/ dist/* + twine upload --repository-url https://test.pypi.org/simple/ dist/* To install your newly uploaded package ``example_pkg``, you can use pip:: - python3 -m pip install --index-url https://test.pypi.org/simple/ example_pkg + pip install --index-url https://test.pypi.org/simple/ example_pkg If you have issues at any point, please refer to `Packaging project tutorials`_ for clarification. -- cgit v1.2.3 From 72993bc1839f6f2ee3e04539a85410d837d3bf98 Mon Sep 17 00:00:00 2001 From: Johannes Reiff Date: Thu, 19 Dec 2019 14:06:22 +0100 Subject: Make easy_install command less strict (fixes #1405) --- setuptools/command/easy_install.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 09066f8c..e979d2aa 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -156,19 +156,16 @@ class easy_install(Command): "allow building eggs from local checkouts"), ('version', None, "print version information and exit"), ('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 = [ 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', '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'} create_index = PackageIndex @@ -272,6 +269,9 @@ class easy_install(Command): self.config_vars['userbase'] = self.install_userbase 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.expand_basedirs() @@ -478,8 +478,9 @@ class easy_install(Command): self.cant_write_to_target() if not is_site_dir and not self.multi_version: - # Can't install non-multi to non-site dir - raise DistutilsError(self.no_default_version_msg()) + # Can't install non-multi to non-site dir with easy_install + pythonpath = os.environ.get('PYTHONPATH', '') + log.warn(self.__no_default_msg, self.install_dir, pythonpath) if is_site_dir: if self.pth_file is None: @@ -1309,10 +1310,6 @@ class easy_install(Command): Please make the appropriate changes for your system and try again.""").lstrip() - 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): """Make sure there's a site.py in the target dir, if needed""" @@ -2344,4 +2341,3 @@ def _patch_usage(): class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" - -- cgit v1.2.3 From ec270f9e13fcc32a2a861273219ebfeba17838df Mon Sep 17 00:00:00 2001 From: Johannes Reiff Date: Thu, 19 Dec 2019 14:42:00 +0100 Subject: Add changelog entry for PR #1941 --- changelog.d/1941.change.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog.d/1941.change.rst diff --git a/changelog.d/1941.change.rst b/changelog.d/1941.change.rst new file mode 100644 index 00000000..a41cdcfe --- /dev/null +++ b/changelog.d/1941.change.rst @@ -0,0 +1,4 @@ +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. -- cgit v1.2.3 From 3910bbb8d57a8f811ce863e9e1d09ae631cfe353 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 21 Dec 2019 22:04:09 -0500 Subject: Extract methods to separate _safe_data_files behavior and _add_data_files. --- setuptools/command/sdist.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 55ecdd97..eebdfd19 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -126,14 +126,27 @@ class sdist(sdist_add_defaults, orig.sdist): if self.distribution.has_pure_modules(): build_py = self.get_finalized_command('build_py') self.filelist.extend(build_py.get_source_files()) - # This functionality is incompatible with include_package_data, and - # will in fact create an infinite recursion if include_package_data - # is True. Use of include_package_data will imply that - # distutils-style automatic handling of package_data is disabled - if not self.distribution.include_package_data: - for _, src_dir, _, filenames in build_py.data_files: - self.filelist.extend([os.path.join(src_dir, filename) - for filename in filenames]) + self._add_data_files(self._safe_data_files(build_py)) + + def _safe_data_files(self, build_py): + """ + Extracting data_files from build_py is known to cause + infinite recursion errors when `include_package_data` + is enabled, so suppress it in that case. + """ + if self.distribution.include_package_data: + return () + return build_py.data_files + + def _add_data_files(self, data_files): + """ + Add data files as found in build_py.data_files. + """ + self.filelist.extend( + os.path.join(src_dir, name) + for _, src_dir, _, filenames in data_files + for name in filenames + ) def _add_defaults_data_files(self): try: -- cgit v1.2.3 From dea5858f1ecf042a17e94a3e26a10bbc78fd2f35 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 29 Dec 2019 11:59:05 -0500 Subject: Add backend-path for future Pips Co-Authored-By: Paul Ganssle --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5a2d7d3b..f0fd8521 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] requires = ["setuptools >= 40.8", "wheel"] build-backend = "setuptools.build_meta" +backend-path = ["."] [tool.towncrier] package = "setuptools" -- cgit v1.2.3 From 8495fb9c59cc9af3a770b7b5ea5f950790e782ed Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 29 Dec 2019 12:21:15 -0500 Subject: Add changelog entry. Ref #1927. --- changelog.d/1927.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1927.change.rst diff --git a/changelog.d/1927.change.rst b/changelog.d/1927.change.rst new file mode 100644 index 00000000..3b293d63 --- /dev/null +++ b/changelog.d/1927.change.rst @@ -0,0 +1 @@ +Setuptools once again declares 'setuptools' in the ``build-system.requires`` and adds PEP 517 build support by declaring itself as the ``build-backend``. It additionally specifies ``build-system.backend-path`` to rely on itself for those builders that support it. -- cgit v1.2.3 From 8a7a6272942c84a0cf59169b84f7434ea4dc4bfe Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 27 Dec 2019 16:12:40 +0800 Subject: Add test ensuring pyproject.toml is included during PEP 517 build. --- setuptools/tests/test_build_meta.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index e1efe561..326b4f5d 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -262,6 +262,27 @@ class TestBuildMetaBackend: assert os.path.isfile( os.path.join(os.path.abspath("out_sdist"), sdist_name)) + def test_build_sdist_pyproject_toml_exists(self, tmpdir_cwd): + files = { + 'setup.py': DALS(""" + __import__('setuptools').setup( + name='foo', + version='0.0.0', + py_modules=['hello'] + )"""), + 'hello.py': '', + 'pyproject.toml': DALS(""" + [build-system] + requires = ["setuptools", "wheel"] + build-backend = "setuptools.build_meta + """), + } + build_files(files) + build_backend = self.get_build_backend() + targz_path = build_backend.build_sdist("temp") + with tarfile.open(os.path.join("temp", targz_path)) as tar: + assert any('pyproject.toml' in name for name in tar.getnames()) + def test_build_sdist_setup_py_exists(self, tmpdir_cwd): # If build_sdist is called from a script other than setup.py, # ensure setup.py is included -- cgit v1.2.3 From 589a70571a890b8113c75916325230b0b832f8c0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 29 Dec 2019 12:57:51 -0500 Subject: Mark the change as a breaking change. --- changelog.d/1634.breaking.rst | 1 + changelog.d/1634.change.rst | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/1634.breaking.rst delete mode 100644 changelog.d/1634.change.rst diff --git a/changelog.d/1634.breaking.rst b/changelog.d/1634.breaking.rst new file mode 100644 index 00000000..b65e5d9f --- /dev/null +++ b/changelog.d/1634.breaking.rst @@ -0,0 +1 @@ +Include ``pyproject.toml`` in source distribution by default. Projects relying on the previous behavior where ``pyproject.toml`` was excluded by default should stop relying on that behavior or add ``exclude pyproject.toml`` to their MANIFEST.in file. diff --git a/changelog.d/1634.change.rst b/changelog.d/1634.change.rst deleted file mode 100644 index 27d0a64a..00000000 --- a/changelog.d/1634.change.rst +++ /dev/null @@ -1 +0,0 @@ -Include ``pyproject.toml`` in source distribution by default. -- cgit v1.2.3 From f171cde1505fc5df438417dc5ae48d35fa60a002 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 12:47:46 -0500 Subject: Add test for exclusion expectation. Ref #1650. --- setuptools/tests/test_sdist.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 06813a00..a413e4ed 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -463,6 +463,22 @@ class TestSdistTest: manifest = cmd.filelist.files assert 'pyproject.toml' in manifest + def test_pyproject_toml_excluded(self): + """ + Check that pyproject.toml can excluded even if present + """ + open(os.path.join(self.temp_dir, 'pyproject.toml'), 'w').close() + with open('MANIFEST.in', 'w') as mts: + print('exclude pyproject.toml', file=mts) + dist = Distribution(SETUP_ATTRS) + dist.script_name = 'setup.py' + cmd = sdist(dist) + cmd.ensure_finalized() + with quiet(): + cmd.run() + manifest = cmd.filelist.files + assert 'pyproject.toml' not in manifest + def test_default_revctrl(): """ -- cgit v1.2.3 From 2eb3ba19f3153f83eb8b2470deb8ec02d21fca52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 12:50:25 -0500 Subject: Restore Python 2.7 compatibility --- setuptools/tests/test_sdist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index a413e4ed..b27c4a83 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """sdist tests""" +from __future__ import print_function + import os import shutil import sys -- cgit v1.2.3 From 47aab6525101bda3e2c7af1588b7abf5f6608b65 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 13:14:50 -0500 Subject: =?UTF-8?q?Bump=20version:=2042.0.2=20=E2=86=92=2043.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 7 +++++++ changelog.d/1634.breaking.rst | 1 - changelog.d/1927.change.rst | 1 - setup.cfg | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/1634.breaking.rst delete mode 100644 changelog.d/1927.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 8a9f4435..25093b87 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 42.0.2 +current_version = 43.0.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 81abbe59..817f8168 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v43.0.0 +------- + +* #1634: Include ``pyproject.toml`` in source distribution by default. Projects relying on the previous behavior where ``pyproject.toml`` was excluded by default should stop relying on that behavior or add ``exclude pyproject.toml`` to their MANIFEST.in file. +* #1927: Setuptools once again declares 'setuptools' in the ``build-system.requires`` and adds PEP 517 build support by declaring itself as the ``build-backend``. It additionally specifies ``build-system.backend-path`` to rely on itself for those builders that support it. + + v42.0.2 ------- diff --git a/changelog.d/1634.breaking.rst b/changelog.d/1634.breaking.rst deleted file mode 100644 index b65e5d9f..00000000 --- a/changelog.d/1634.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -Include ``pyproject.toml`` in source distribution by default. Projects relying on the previous behavior where ``pyproject.toml`` was excluded by default should stop relying on that behavior or add ``exclude pyproject.toml`` to their MANIFEST.in file. diff --git a/changelog.d/1927.change.rst b/changelog.d/1927.change.rst deleted file mode 100644 index 3b293d63..00000000 --- a/changelog.d/1927.change.rst +++ /dev/null @@ -1 +0,0 @@ -Setuptools once again declares 'setuptools' in the ``build-system.requires`` and adds PEP 517 build support by declaring itself as the ``build-backend``. It additionally specifies ``build-system.backend-path`` to rely on itself for those builders that support it. diff --git a/setup.cfg b/setup.cfg index 68b49d73..a9a9b504 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 42.0.2 +version = 43.0.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 9c40ab8861d1bbc18d1c8032f678e2ca15ada7ff Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 13:29:43 -0500 Subject: Rewrite TestSdistTest setup/teardown_method as pytest fixture. --- setuptools/tests/test_sdist.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index b27c4a83..f2e9a5ec 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -91,30 +91,29 @@ fail_on_latin1_encoded_filenames = pytest.mark.xfail( ) +def touch(path): + path.write_text('', encoding='utf-8') + + class TestSdistTest: - def setup_method(self, method): - self.temp_dir = tempfile.mkdtemp() - with open(os.path.join(self.temp_dir, 'setup.py'), 'w') as f: - f.write(SETUP_PY) + @pytest.fixture(autouse=True) + def source_dir(self, tmpdir): + self.temp_dir = str(tmpdir) + (tmpdir / 'setup.py').write_text(SETUP_PY, encoding='utf-8') # Set up the rest of the test package - test_pkg = os.path.join(self.temp_dir, 'sdist_test') - os.mkdir(test_pkg) - data_folder = os.path.join(self.temp_dir, "d") - os.mkdir(data_folder) + test_pkg = tmpdir / 'sdist_test' + test_pkg.mkdir() + data_folder = tmpdir / 'd' + data_folder.mkdir() # *.rst was not included in package_data, so c.rst should not be # automatically added to the manifest when not under version control - for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst', - os.path.join(data_folder, "e.dat")]: - # Just touch the files; their contents are irrelevant - open(os.path.join(test_pkg, fname), 'w').close() - - self.old_cwd = os.getcwd() - os.chdir(self.temp_dir) + for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']: + touch(test_pkg / fname) + touch(data_folder / 'e.dat') - def teardown_method(self, method): - os.chdir(self.old_cwd) - shutil.rmtree(self.temp_dir) + with tmpdir.as_cwd(): + yield def test_package_data_in_sdist(self): """Regression test for pull request #4: ensures that files listed in -- cgit v1.2.3 From 9e3149802ee214ee0500ec299250bf4febc67e52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 13:31:37 -0500 Subject: Remove instance attribute; rely on tmpdir fixture; re-use touch helper. --- setuptools/tests/test_sdist.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index f2e9a5ec..1b951a5b 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -98,7 +98,6 @@ def touch(path): class TestSdistTest: @pytest.fixture(autouse=True) def source_dir(self, tmpdir): - self.temp_dir = str(tmpdir) (tmpdir / 'setup.py').write_text(SETUP_PY, encoding='utf-8') # Set up the rest of the test package @@ -176,14 +175,14 @@ class TestSdistTest: manifest = cmd.filelist.files assert 'setup.py' not in manifest - def test_defaults_case_sensitivity(self): + def test_defaults_case_sensitivity(self, tmpdir): """ Make sure default files (README.*, etc.) are added in a case-sensitive way to avoid problems with packages built on Windows. """ - open(os.path.join(self.temp_dir, 'readme.rst'), 'w').close() - open(os.path.join(self.temp_dir, 'SETUP.cfg'), 'w').close() + touch(tmpdir / 'readme.rst') + touch(tmpdir / 'SETUP.cfg') dist = Distribution(SETUP_ATTRS) # the extension deliberately capitalized for this test @@ -450,11 +449,11 @@ class TestSdistTest: except UnicodeDecodeError: filename not in cmd.filelist.files - def test_pyproject_toml_in_sdist(self): + def test_pyproject_toml_in_sdist(self, tmpdir): """ Check if pyproject.toml is included in source distribution if present """ - open(os.path.join(self.temp_dir, 'pyproject.toml'), 'w').close() + touch(tmpdir / 'pyproject.toml') dist = Distribution(SETUP_ATTRS) dist.script_name = 'setup.py' cmd = sdist(dist) @@ -464,11 +463,11 @@ class TestSdistTest: manifest = cmd.filelist.files assert 'pyproject.toml' in manifest - def test_pyproject_toml_excluded(self): + def test_pyproject_toml_excluded(self, tmpdir): """ Check that pyproject.toml can excluded even if present """ - open(os.path.join(self.temp_dir, 'pyproject.toml'), 'w').close() + touch(tmpdir / 'pyproject.toml') with open('MANIFEST.in', 'w') as mts: print('exclude pyproject.toml', file=mts) dist = Distribution(SETUP_ATTRS) -- cgit v1.2.3 From a87f975e65507382aaecfb01fe8df4608c38f466 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 31 Dec 2019 13:32:57 -0500 Subject: Remove unused import --- setuptools/tests/test_sdist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 1b951a5b..dcc64cf2 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -4,7 +4,6 @@ from __future__ import print_function import os -import shutil import sys import tempfile import unicodedata -- cgit v1.2.3 From 90922a5eb9b2f002202a16c974b86750a46d21ea Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 1 Jan 2020 11:54:53 -0500 Subject: Restore Python 2.7 compatibility --- setuptools/tests/test_sdist.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index dcc64cf2..9ddbae8b 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """sdist tests""" -from __future__ import print_function +from __future__ import print_function, unicode_literals import os import sys @@ -229,10 +229,6 @@ class TestSdistTest: u_contents = contents.decode('UTF-8') # The manifest should contain the UTF-8 filename - if six.PY2: - fs_enc = sys.getfilesystemencoding() - filename = filename.decode(fs_enc) - assert posix(filename) in u_contents @py3_only -- cgit v1.2.3 From 3d5b7775b7b7ee6c0b354a04fe1d33c1f9b0e5df Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 1 Jan 2020 12:08:46 -0500 Subject: Fix latin1 and utf8 tests on Python 2 --- setuptools/tests/test_sdist.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 9ddbae8b..8538dd24 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -369,7 +369,7 @@ class TestSdistTest: @fail_on_latin1_encoded_filenames def test_sdist_with_utf8_encoded_filename(self): # Test for #303. - dist = Distribution(SETUP_ATTRS) + dist = Distribution(self.make_strings(SETUP_ATTRS)) dist.script_name = 'setup.py' cmd = sdist(dist) cmd.ensure_finalized() @@ -400,10 +400,19 @@ class TestSdistTest: else: assert filename in cmd.filelist.files + @classmethod + def make_strings(cls, item): + if isinstance(item, dict): + return { + key: cls.make_strings(value) for key, value in item.items()} + if isinstance(item, list): + return list(map(cls.make_strings, item)) + return str(item) + @fail_on_latin1_encoded_filenames def test_sdist_with_latin1_encoded_filename(self): # Test for #303. - dist = Distribution(SETUP_ATTRS) + dist = Distribution(self.make_strings(SETUP_ATTRS)) dist.script_name = 'setup.py' cmd = sdist(dist) cmd.ensure_finalized() -- cgit v1.2.3 From 7e97def47723303fafabe48b22168bbc11bb4821 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 1 Jan 2020 18:33:05 -0500 Subject: =?UTF-8?q?Bump=20version:=2043.0.0=20=E2=86=92=2044.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 6 ++++++ changelog.d/1908.breaking.rst | 1 - setup.cfg | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 changelog.d/1908.breaking.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 25093b87..e1bfa898 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 43.0.0 +current_version = 44.0.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 817f8168..109a3f48 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v44.0.0 +------- + +* #1908: Drop support for Python 3.4. + + v43.0.0 ------- diff --git a/changelog.d/1908.breaking.rst b/changelog.d/1908.breaking.rst deleted file mode 100644 index 3fbb9fe7..00000000 --- a/changelog.d/1908.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -Drop support for Python 3.4. diff --git a/setup.cfg b/setup.cfg index ed084084..ecef8609 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 43.0.0 +version = 44.0.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 98b7bab4d235e982526794bcec90827ac300f5bc Mon Sep 17 00:00:00 2001 From: Gabriel Date: Thu, 2 Jan 2020 01:02:21 +0100 Subject: Update setuptools.txt --- docs/setuptools.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 03b57cf3..7741ec07 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1000,11 +1000,11 @@ and Python Eggs. It is strongly recommended that, if you are using data files, you should use the :ref:`ResourceManager API` of ``pkg_resources`` to access them. The ``pkg_resources`` module is distributed as part of setuptools, so if you're using setuptools to distribute your package, there is no reason not to -use its resource management API. See also `Accessing Package Resources`_ for +use its resource management API. See also `Importlib Resources`_ for a quick example of converting code that uses ``__file__`` to use ``pkg_resources`` instead. -.. _Accessing Package Resources: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources +.. _Importlib Resources: https://docs.python.org/3/library/importlib.html#module-importlib.resources Non-Package Data Files -- cgit v1.2.3 From a46a6bfd903ecc292fc3645c37c1b72781528095 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 2 Jan 2020 10:53:17 -0500 Subject: Require Python 3.5 or later, dropping support for Python 2. This change does not yet remove any of the compatibility for Python 2, but only aims to declare the dropped support. --- .travis.yml | 8 +------- appveyor.yml | 4 ++-- changelog.d/1458.breaking.rst | 1 + setup.cfg | 4 +--- 4 files changed, 5 insertions(+), 12 deletions(-) create mode 100644 changelog.d/1458.breaking.rst diff --git a/.travis.yml b/.travis.yml index 501a0b69..d18b86c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,8 @@ language: python jobs: fast_finish: true include: - - &latest_py2 - python: 2.7 - - <<: *latest_py2 - env: LANG=C - - python: pypy - env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - python: pypy3 - env: DISABLE_COVERAGE=1 + env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - python: 3.5 - python: 3.6 - python: 3.7 diff --git a/appveyor.yml b/appveyor.yml index 08818069..fc65a9a7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,8 +9,8 @@ environment: matrix: - APPVEYOR_JOB_NAME: "python36-x64" PYTHON: "C:\\Python36-x64" - - APPVEYOR_JOB_NAME: "python27-x64" - PYTHON: "C:\\Python27-x64" + - APPVEYOR_JOB_NAME: "python37-x64" + PYTHON: "C:\\Python37-x64" install: # symlink python from a directory with a space diff --git a/changelog.d/1458.breaking.rst b/changelog.d/1458.breaking.rst new file mode 100644 index 00000000..3004722c --- /dev/null +++ b/changelog.d/1458.breaking.rst @@ -0,0 +1 @@ +Drop support for Python 2. Setuptools now requires Python 3.5 or later. Install setuptools using pip >=9 or pin to Setuptools <45 to maintain 2.7 support. diff --git a/setup.cfg b/setup.cfg index ecef8609..1e23051f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,8 +35,6 @@ classifiers = Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: OS Independent - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 @@ -49,7 +47,7 @@ classifiers = [options] zip_safe = True -python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* +python_requires = >=3.5 py_modules = easy_install packages = find: -- cgit v1.2.3 From 79f1694b05a66cc0fbbbf4e72d63d0a340cf6d84 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 3 Jan 2020 06:30:03 -0500 Subject: Add obnoxious warning about Python 2 being unsupported on this release with guidance on how to avoid the warning and what to do if that guidance was ineffective. --- pkg_resources/__init__.py | 1 + pkg_resources/py2_warn.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 pkg_resources/py2_warn.py diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 2f5aa64a..3fa883ce 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -83,6 +83,7 @@ __import__('pkg_resources.extern.packaging.version') __import__('pkg_resources.extern.packaging.specifiers') __import__('pkg_resources.extern.packaging.requirements') __import__('pkg_resources.extern.packaging.markers') +__import__('pkg_resources.py2_warn') __metaclass__ = type diff --git a/pkg_resources/py2_warn.py b/pkg_resources/py2_warn.py new file mode 100644 index 00000000..1f29851c --- /dev/null +++ b/pkg_resources/py2_warn.py @@ -0,0 +1,19 @@ +import sys +import warnings +import textwrap + + +msg = textwrap.dedent(""" + You are running Setuptools on Python 2, which is no longer + supported and + >>> SETUPTOOLS WILL STOP WORKING <<< + in a subsequent release. Please ensure you are installing + Setuptools using pip 9.x or later or pin to `setuptools<45` + in your environment. + If you have done those things and are still encountering + this message, please comment in + https://github.com/pypa/setuptools/issues/1458 + about the steps that led to this unsupported combination. + """) + +sys.version_info < (3,) and warnings.warn("*" * 60 + msg + "*" * 60) -- cgit v1.2.3 From 073ad9711dc9f750b974fa7a76c832603af2efa6 Mon Sep 17 00:00:00 2001 From: Dustin Ingram Date: Mon, 6 Jan 2020 11:14:03 -0600 Subject: Fix TestPyPI upload URI --- docs/setuptools.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 03b57cf3..11faf041 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1233,7 +1233,7 @@ You should install twine to be able to upload packages:: Now, to upload these archives, run:: - twine upload --repository-url https://test.pypi.org/simple/ dist/* + twine upload --repository-url https://test.pypi.org/legacy/ dist/* To install your newly uploaded package ``example_pkg``, you can use pip:: -- cgit v1.2.3 From 796abd8dbec884cedf326cb5f85512a5d5648c4e Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 8 Jan 2020 19:10:11 +0200 Subject: Fix for Python 4: replace unsafe six.PY3 with PY2 --- setuptools/command/build_ext.py | 2 +- setuptools/command/develop.py | 2 +- setuptools/command/easy_install.py | 2 +- setuptools/command/egg_info.py | 2 +- setuptools/command/sdist.py | 2 +- setuptools/command/test.py | 4 ++-- setuptools/command/upload_docs.py | 4 ++-- setuptools/dist.py | 6 +++--- setuptools/tests/test_develop.py | 4 ++-- setuptools/tests/test_sdist.py | 32 ++++++++++++++++---------------- setuptools/tests/test_setopt.py | 2 +- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index daa8e4fe..1b51e040 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -113,7 +113,7 @@ class build_ext(_build_ext): if fullname in self.ext_map: ext = self.ext_map[fullname] use_abi3 = ( - six.PY3 + not six.PY2 and getattr(ext, 'py_limited_api') and get_abi3_suffix() ) diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index 009e4f93..b5619246 100644 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -108,7 +108,7 @@ class develop(namespaces.DevelopInstaller, easy_install): return path_to_setup def install_for_development(self): - if six.PY3 and getattr(self.distribution, 'use_2to3', False): + if not six.PY2 and getattr(self.distribution, 'use_2to3', False): # If we run 2to3 we can not do this inplace: # Ensure metadata is up-to-date diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 09066f8c..426301d6 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -1567,7 +1567,7 @@ def get_exe_prefixes(exe_filename): continue if parts[0].upper() in ('PURELIB', 'PLATLIB'): contents = z.read(name) - if six.PY3: + if not six.PY2: contents = contents.decode() for pth in yield_lines(contents): pth = pth.strip().replace('\\', '/') diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 5d8f451e..a5c5a2fc 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -266,7 +266,7 @@ class egg_info(InfoCommon, Command): to the file. """ log.info("writing %s to %s", what, filename) - if six.PY3: + if not six.PY2: data = data.encode("utf-8") if not self.dry_run: f = open(filename, 'wb') diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index a851453f..8c3438ea 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -207,7 +207,7 @@ class sdist(sdist_add_defaults, orig.sdist): manifest = open(self.manifest, 'rb') for line in manifest: # The manifest must contain UTF-8. See #303. - if six.PY3: + if not six.PY2: try: line = line.decode('UTF-8') except UnicodeDecodeError: diff --git a/setuptools/command/test.py b/setuptools/command/test.py index c148b38d..f6470e9c 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -129,7 +129,7 @@ class test(Command): @contextlib.contextmanager def project_on_sys_path(self, include_dists=[]): - with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) + with_2to3 = not six.PY2 and getattr(self.distribution, 'use_2to3', False) if with_2to3: # If we run 2to3 we can not do this inplace: @@ -240,7 +240,7 @@ class test(Command): # Purge modules under test from sys.modules. The test loader will # re-import them from the build location. Required when 2to3 is used # with namespace packages. - if six.PY3 and getattr(self.distribution, 'use_2to3', False): + if not six.PY2 and getattr(self.distribution, 'use_2to3', False): module = self.test_suite.split('.')[0] if module in _namespace_packages: del_modules = [] diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 07aa564a..130a0cb6 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -24,7 +24,7 @@ from .upload import upload def _encode(s): - errors = 'surrogateescape' if six.PY3 else 'strict' + errors = 'strict' if six.PY2 else 'surrogateescape' return s.encode('utf-8', errors) @@ -153,7 +153,7 @@ class upload_docs(upload): # set up the authentication credentials = _encode(self.username + ':' + self.password) credentials = standard_b64encode(credentials) - if six.PY3: + if not six.PY2: credentials = credentials.decode('ascii') auth = "Basic " + credentials diff --git a/setuptools/dist.py b/setuptools/dist.py index 1ba262ec..fe5adf46 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -571,7 +571,7 @@ class Distribution(_Distribution): from setuptools.extern.six.moves.configparser import ConfigParser # Ignore install directory options if we have a venv - if six.PY3 and sys.prefix != sys.base_prefix: + if not six.PY2 and sys.prefix != sys.base_prefix: ignore_options = [ 'install-base', 'install-platbase', 'install-lib', 'install-platlib', 'install-purelib', 'install-headers', @@ -593,7 +593,7 @@ class Distribution(_Distribution): with io.open(filename, encoding='utf-8') as reader: if DEBUG: self.announce(" reading {filename}".format(**locals())) - (parser.read_file if six.PY3 else parser.readfp)(reader) + (parser.readfp if six.PY2 else parser.read_file)(reader) for section in parser.sections(): options = parser.options(section) opt_dict = self.get_option_dict(section) @@ -636,7 +636,7 @@ class Distribution(_Distribution): Ref #1653 """ - if six.PY3: + if not six.PY2: return val try: return val.encode() diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py index 00d4bd9a..792975fd 100644 --- a/setuptools/tests/test_develop.py +++ b/setuptools/tests/test_develop.py @@ -95,7 +95,7 @@ class TestDevelop: with io.open(fn) as init_file: init = init_file.read().strip() - expected = 'print("foo")' if six.PY3 else 'print "foo"' + expected = 'print "foo"' if six.PY2 else 'print("foo")' assert init == expected def test_console_scripts(self, tmpdir): @@ -161,7 +161,7 @@ class TestNamespaces: reason="https://github.com/pypa/setuptools/issues/851", ) @pytest.mark.skipif( - platform.python_implementation() == 'PyPy' and six.PY3, + platform.python_implementation() == 'PyPy' and not six.PY2, reason="https://github.com/pypa/setuptools/issues/1202", ) def test_namespace_package_importable(self, tmpdir): diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 8538dd24..0bea53df 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -51,7 +51,7 @@ def quiet(): # Convert to POSIX path def posix(path): - if six.PY3 and not isinstance(path, str): + if not six.PY2 and not isinstance(path, str): return path.replace(os.sep.encode('ascii'), b'/') else: return path.replace(os.sep, '/') @@ -329,7 +329,7 @@ class TestSdistTest: cmd.read_manifest() # The filelist should contain the UTF-8 filename - if six.PY3: + if not six.PY2: filename = filename.decode('utf-8') assert filename in cmd.filelist.files @@ -383,7 +383,7 @@ class TestSdistTest: if sys.platform == 'darwin': filename = decompose(filename) - if six.PY3: + if not six.PY2: fs_enc = sys.getfilesystemencoding() if sys.platform == 'win32': @@ -425,7 +425,19 @@ class TestSdistTest: with quiet(): cmd.run() - if six.PY3: + if six.PY2: + # Under Python 2 there seems to be no decoded string in the + # filelist. However, due to decode and encoding of the + # file name to get utf-8 Manifest the latin1 maybe excluded + try: + # fs_enc should match how one is expect the decoding to + # be proformed for the manifest output. + fs_enc = sys.getfilesystemencoding() + filename.decode(fs_enc) + assert filename in cmd.filelist.files + except UnicodeDecodeError: + filename not in cmd.filelist.files + else: # not all windows systems have a default FS encoding of cp1252 if sys.platform == 'win32': # Latin-1 is similar to Windows-1252 however @@ -440,18 +452,6 @@ class TestSdistTest: # The Latin-1 filename should have been skipped filename = filename.decode('latin-1') filename not in cmd.filelist.files - else: - # Under Python 2 there seems to be no decoded string in the - # filelist. However, due to decode and encoding of the - # file name to get utf-8 Manifest the latin1 maybe excluded - try: - # fs_enc should match how one is expect the decoding to - # be proformed for the manifest output. - fs_enc = sys.getfilesystemencoding() - filename.decode(fs_enc) - assert filename in cmd.filelist.files - except UnicodeDecodeError: - filename not in cmd.filelist.files def test_pyproject_toml_in_sdist(self, tmpdir): """ diff --git a/setuptools/tests/test_setopt.py b/setuptools/tests/test_setopt.py index 3fb04fb4..1b038954 100644 --- a/setuptools/tests/test_setopt.py +++ b/setuptools/tests/test_setopt.py @@ -15,7 +15,7 @@ class TestEdit: def parse_config(filename): parser = configparser.ConfigParser() with io.open(filename, encoding='utf-8') as reader: - (parser.read_file if six.PY3 else parser.readfp)(reader) + (parser.readfp if six.PY2 else parser.read_file)(reader) return parser @staticmethod -- cgit v1.2.3 From b84a0997af9b5ba757d39b0631545f53d03bc741 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 8 Jan 2020 19:21:05 +0200 Subject: Add changelog --- changelog.d/1959.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1959.change.rst diff --git a/changelog.d/1959.change.rst b/changelog.d/1959.change.rst new file mode 100644 index 00000000..c0cc8975 --- /dev/null +++ b/changelog.d/1959.change.rst @@ -0,0 +1 @@ +Fix for Python 4: replace unsafe six.PY3 with six.PY2 -- cgit v1.2.3 From c30a9652fb3bcf941ba17ccda3f577e0c4d99d07 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Jan 2020 23:35:41 -0500 Subject: =?UTF-8?q?Bump=20version:=2044.0.0=20=E2=86=92=2045.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 7 +++++++ changelog.d/1458.breaking.rst | 1 - changelog.d/1959.change.rst | 1 - setup.cfg | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/1458.breaking.rst delete mode 100644 changelog.d/1959.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e1bfa898..77143907 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 44.0.0 +current_version = 45.0.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 109a3f48..4a81e995 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v45.0.0 +------- + +* #1458: Drop support for Python 2. Setuptools now requires Python 3.5 or later. Install setuptools using pip >=9 or pin to Setuptools <45 to maintain 2.7 support. +* #1959: Fix for Python 4: replace unsafe six.PY3 with six.PY2 + + v44.0.0 ------- diff --git a/changelog.d/1458.breaking.rst b/changelog.d/1458.breaking.rst deleted file mode 100644 index 3004722c..00000000 --- a/changelog.d/1458.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -Drop support for Python 2. Setuptools now requires Python 3.5 or later. Install setuptools using pip >=9 or pin to Setuptools <45 to maintain 2.7 support. diff --git a/changelog.d/1959.change.rst b/changelog.d/1959.change.rst deleted file mode 100644 index c0cc8975..00000000 --- a/changelog.d/1959.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fix for Python 4: replace unsafe six.PY3 with six.PY2 diff --git a/setup.cfg b/setup.cfg index 1e23051f..18c9e1df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,7 @@ universal = 1 [metadata] name = setuptools -version = 44.0.0 +version = 45.0.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From 6cb025eadfbc6bf017ba2bfd80c192ac377be9fb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 12 Jan 2020 11:23:43 -0500 Subject: Rely on tox-pip-version to upgrade pip and minimize the hack for removing setuptools from the environment. --- tools/tox_pip.py | 37 +++++++++++++++---------------------- tox.ini | 8 +++++--- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 63518f92..f592e412 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -1,31 +1,24 @@ -import os -import shutil import subprocess import sys -from glob import glob -VIRTUAL_ENV = os.environ['VIRTUAL_ENV'] -TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip') + +def remove_setuptools(): + """ + Remove setuptools from the current environment. + """ + print("Removing setuptools") + cmd = [sys.executable, '-m', 'pip', 'uninstall', '-y', 'setuptools'] + # set cwd to something other than '.' to avoid detecting + # '.' as the installed package. + subprocess.check_call(cmd, cwd='.tox') def pip(args): - # First things first, get a recent (stable) version of pip. - if not os.path.exists(TOX_PIP_DIR): - subprocess.check_call([sys.executable, '-m', 'pip', - '--disable-pip-version-check', - 'install', '-t', TOX_PIP_DIR, - 'pip']) - shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0]) - # And use that version. - pypath = os.environ.get('PYTHONPATH') - pypath = pypath.split(os.pathsep) if pypath is not None else [] - pypath.insert(0, TOX_PIP_DIR) - os.environ['PYTHONPATH'] = os.pathsep.join(pypath) - # Fix call for setuptools editable install. - for n, a in enumerate(args): - if a == '.': - args[n] = os.getcwd() - subprocess.check_call([sys.executable, '-m', 'pip'] + args, cwd=TOX_PIP_DIR) + # When installing '.', remove setuptools + '.' in args and remove_setuptools() + + cmd = [sys.executable, '-m', 'pip'] + args + subprocess.check_call(cmd) if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index 6a1af56e..a666f0af 100644 --- a/tox.ini +++ b/tox.ini @@ -6,15 +6,17 @@ [tox] envlist=python +minversion = 3.2 +requires = + tox-pip-version >= 0.0.6 [helpers] -# Wrapper for calls to pip that make sure the version being used is a -# up-to-date, and to prevent the current working directory from being -# added to `sys.path`. +# Custom pip behavior pip = python {toxinidir}/tools/tox_pip.py [testenv] deps=-r{toxinidir}/tests/requirements.txt +pip_version = pip install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} -- cgit v1.2.3 From 8e6b9933e9981fc9ab19eef3dee93d0f703c4140 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 12 Jan 2020 10:13:09 -0500 Subject: Restore testing on Python 2, bypassing the requires-python check when installing setuptools. --- .travis.yml | 4 ++++ tools/tox_pip.py | 5 +++++ tox.ini | 4 +++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d18b86c0..24b2451b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ language: python jobs: fast_finish: true include: + - &latest_py2 + python: 2.7 + - <<: *latest_py2 + env: LANG=C - python: pypy3 env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - python: 3.5 diff --git a/tools/tox_pip.py b/tools/tox_pip.py index f592e412..06655fe4 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -1,3 +1,4 @@ +import os import subprocess import sys @@ -14,6 +15,10 @@ def remove_setuptools(): def pip(args): + # Honor requires-python when installing test suite dependencies + if any('-r' in arg for arg in args): + os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = '0' + # When installing '.', remove setuptools '.' in args and remove_setuptools() diff --git a/tox.ini b/tox.ini index a666f0af..d458dc33 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,9 @@ deps=-r{toxinidir}/tests/requirements.txt pip_version = pip install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all -setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname} +setenv = + COVERAGE_FILE={toxworkdir}/.coverage.{envname} + py27: PIP_IGNORE_REQUIRES_PYTHON=true # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED -- cgit v1.2.3 From cb2138e24bc9a91e533c4596c125afa6f3eb5c6c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 12 Jan 2020 12:38:15 -0500 Subject: Set toxenv for Python 2.7 so that workaround is present. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 24b2451b..263386c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,9 @@ jobs: include: - &latest_py2 python: 2.7 + env: TOXENV=py27 - <<: *latest_py2 - env: LANG=C + env: LANG=C TOXENV=py27 - python: pypy3 env: DISABLE_COVERAGE=1 # Don't run coverage on pypy (too slow). - python: 3.5 -- cgit v1.2.3 From e5e5ab2c080d0e9dc99470ec9f2ba21bdf141b80 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sun, 12 Jan 2020 19:23:14 +0100 Subject: Add Python 3.8 to the test matrix Co-Authored-By: Hugo van Kemenade --- .github/workflows/python-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index eb750a45..f315e6fe 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -14,6 +14,7 @@ jobs: # max-parallel: 5 matrix: python-version: + - 3.8 - 3.7 - 3.6 - 3.5 -- cgit v1.2.3 From 23dce8ba4931084813e90b5837db2c20135082fa Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sun, 12 Jan 2020 19:24:38 +0100 Subject: Upgrade the macOS VMs to use a new supported version This change is necessary because macOS 10.15 is now deprecated. Ref: https://github.blog/changelog/2019-10-31-github-actions-macos-virtual-environment-is-updating-to-catalina-and-dropping-mojave-support Co-Authored-By: Hugo van Kemenade --- .github/workflows/python-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index f315e6fe..71957aba 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -22,7 +22,7 @@ jobs: os: - ubuntu-18.04 - ubuntu-16.04 - - macOS-10.14 + - macOS-latest # - windows-2019 # - windows-2016 env: -- cgit v1.2.3 From 0cd3dfc3abb1c638578bf9b540d930ddad6d19c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Mon, 13 Jan 2020 19:52:25 +0100 Subject: Fix typos --- docs/setuptools.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index d214ca99..a1d927d5 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -1800,7 +1800,7 @@ to support "daily builds" or "snapshot" releases. It is run automatically by 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 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.) In addition to writing the core egg metadata defined by ``setuptools`` and @@ -1848,7 +1848,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 like pip. See the section above on `Specifying Your Project's Version`_ for an explanation of pre- and post-release tags, as well as tips on how to choose and -verify a versioning scheme for your your project.) +verify a versioning scheme for your project.) For advanced uses, there is one other option that can be set, to change the location of the project's ``.egg-info`` directory. Commands that need to find -- cgit v1.2.3 From 18a3cae3513818d355dbc8c05ff93bbcee09a6d5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Jan 2020 04:14:13 -0500 Subject: Update Python 2 warning to include a minimum sunset date and add a preamble to make referencing the warning more reliable. Ref #1458. --- changelog.d/1458.change.rst | 1 + pkg_resources/py2_warn.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1458.change.rst diff --git a/changelog.d/1458.change.rst b/changelog.d/1458.change.rst new file mode 100644 index 00000000..c953127a --- /dev/null +++ b/changelog.d/1458.change.rst @@ -0,0 +1 @@ +Add minimum sunset date and preamble to Python 2 warning. diff --git a/pkg_resources/py2_warn.py b/pkg_resources/py2_warn.py index 1f29851c..1b151956 100644 --- a/pkg_resources/py2_warn.py +++ b/pkg_resources/py2_warn.py @@ -7,7 +7,8 @@ msg = textwrap.dedent(""" You are running Setuptools on Python 2, which is no longer supported and >>> SETUPTOOLS WILL STOP WORKING <<< - in a subsequent release. Please ensure you are installing + in a subsequent release (no sooner than 2020-04-20). + Please ensure you are installing Setuptools using pip 9.x or later or pin to `setuptools<45` in your environment. If you have done those things and are still encountering @@ -16,4 +17,6 @@ msg = textwrap.dedent(""" about the steps that led to this unsupported combination. """) -sys.version_info < (3,) and warnings.warn("*" * 60 + msg + "*" * 60) +pre = "Setuptools will stop working on Python 2\n" + +sys.version_info < (3,) and warnings.warn(pre + "*" * 60 + msg + "*" * 60) -- cgit v1.2.3 From 9afceaf1e5caf499ac78735b4393e8e2f3e9b2d9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 14 Jan 2020 14:16:03 +0200 Subject: Add flake8-2020 to requirements.txt --- tests/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index 4b5e0eeb..19bf5aef 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,6 @@ mock pytest-flake8 +flake8-2020; python_version>="3.6" virtualenv>=13.0.0 pytest-virtualenv>=1.2.7 pytest>=3.7 -- cgit v1.2.3 From 4ea498b752fc89fd47c795f46fa1ff66c314dc58 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 14 Jan 2020 14:22:50 +0200 Subject: Add changelog --- changelog.d/1968.misc.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1968.misc.rst diff --git a/changelog.d/1968.misc.rst b/changelog.d/1968.misc.rst new file mode 100644 index 00000000..4aa5343f --- /dev/null +++ b/changelog.d/1968.misc.rst @@ -0,0 +1 @@ +Add flake8-2020 to check for misuse of sys.version or sys.version_info. -- cgit v1.2.3 From 2ce065e44bfb5bd9b3d8589efdb57876b170e7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 15 Jan 2020 18:13:31 +0100 Subject: Remove the python command from setup.py calls --- docs/setuptools.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index a1d927d5..6798a5a5 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -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 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 more information to your setup script to help people find or learn about your @@ -1220,7 +1220,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 setup.py is located:: - python3 setup.py sdist bdist_wheel + setup.py sdist bdist_wheel This will generate distribution archives in the `dist` directory. @@ -1469,7 +1469,7 @@ tagging the release, so the trunk will still produce development snapshots. Alternately, if you are not branching for releases, you can override the 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 configured tag information, before creating source and binary eggs. Thus, these @@ -1479,11 +1479,11 @@ build designation string. Of course, if you will be doing this a lot, you may wish to create a personal 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:: - 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. See the sections below on the `egg_info`_ and `alias`_ commands for more ideas. @@ -1873,12 +1873,12 @@ Other ``egg_info`` Options 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 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 commands that you want the version changes to apply to.) -- cgit v1.2.3 From 1e5fa9b30a3c3d65d2767d5b9928e555b8c32713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Ogam?= Date: Wed, 15 Jan 2020 18:23:38 +0100 Subject: Uniformise quotation marks --- docs/setuptools.txt | 104 ++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/docs/setuptools.txt b/docs/setuptools.txt index 6798a5a5..f84837ff 100644 --- a/docs/setuptools.txt +++ b/docs/setuptools.txt @@ -100,17 +100,17 @@ dependencies, and perhaps some data files and scripts:: name="HelloWorld", version="0.1", packages=find_packages(), - scripts=['say_hello.py'], + scripts=["say_hello.py"], # Project uses reStructuredText, so ensure that the docutils get # installed or upgraded on the target machine - install_requires=['docutils>=0.3'], + install_requires=["docutils>=0.3"], package_data={ # If any package contains *.txt or *.rst files, include them: - '': ['*.txt', '*.rst'], - # And include any *.msg files found in the 'hello' package, too: - 'hello': ['*.msg'], + "": ["*.txt", "*.rst"], + # And include any *.msg files found in the "hello" package, too: + "hello": ["*.msg"], }, # metadata to display on PyPI @@ -125,7 +125,7 @@ dependencies, and perhaps some data files and scripts:: "Source Code": "https://code.example.com/HelloWorld/", }, classifiers=[ - 'License :: OSI Approved :: Python Software Foundation License' + "License :: OSI Approved :: Python Software Foundation License" ] # 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: to compare different version numbers:: >>> 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 - >>> parse_version('2.1-rc2') < parse_version('2.1') + >>> parse_version("2.1-rc2") < parse_version("2.1") True - >>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9') + >>> parse_version("0.6a9dev-r41475") < parse_version("0.6a9") True 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. 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 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 ``bar/baz/foo.png`` in this argument. @@ -413,7 +413,7 @@ the same 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 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.) 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 setup( name="namespace.mypackage", 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 @@ -500,8 +500,8 @@ With this layout, the package directory is specified as ``src``, as such:: setup(name="namespace.mypackage", version="0.1", - package_dir={'': 'src'}, - packages=find_namespace_packages(where='src')) + package_dir={"": "src"}, + packages=find_namespace_packages(where="src")) .. _PEP 420: https://www.python.org/dev/peps/pep-0420/ @@ -526,12 +526,12 @@ script called ``baz``, you might do something like this:: setup( # other arguments here... entry_points={ - 'console_scripts': [ - 'foo = my_package.some_module:main_func', - 'bar = other_module:some_func', + "console_scripts": [ + "foo = my_package.some_module:main_func", + "bar = other_module:some_func", ], - 'gui_scripts': [ - 'baz = my_package_gui:start_func', + "gui_scripts": [ + "baz = my_package_gui:start_func", ] } ) @@ -567,8 +567,8 @@ as the following:: setup( # other arguments here... entry_points={ - 'setuptools.installation': [ - 'eggsecutable = my_package.some_module:main_func', + "setuptools.installation": [ + "eggsecutable = my_package.some_module:main_func", ] } ) @@ -741,8 +741,8 @@ For example, let's say that Project A offers optional PDF and reST support:: name="Project-A", ... extras_require={ - 'PDF': ["ReportLab>=1.2", "RXP"], - 'reST': ["docutils>=0.3"], + "PDF": ["ReportLab>=1.2", "RXP"], + "reST": ["docutils>=0.3"], } ) @@ -763,9 +763,9 @@ declare it like this, so that the "PDF" requirements are only resolved if the name="Project-A", ... entry_points={ - 'console_scripts': [ - 'rst2pdf = project_a.tools.pdfgen [PDF]', - 'rst2html = project_a.tools.htmlgen', + "console_scripts": [ + "rst2pdf = project_a.tools.pdfgen [PDF]", + "rst2html = project_a.tools.htmlgen", # more script entry points ... ], } @@ -801,8 +801,8 @@ setup to this:: name="Project-A", ... extras_require={ - 'PDF': [], - 'reST': ["docutils>=0.3"], + "PDF": [], + "reST": ["docutils>=0.3"], } ) @@ -829,8 +829,8 @@ For example, here is a project that uses the ``enum`` module and ``pywin32``:: name="Project", ... install_requires=[ - 'enum34;python_version<"3.4"', - 'pywin32 >= 1.0;platform_system=="Windows"' + "enum34;python_version<'3.4'", + "pywin32 >= 1.0;platform_system=='Windows'" ] ) @@ -878,9 +878,9 @@ e.g.:: ... package_data={ # If any package contains *.txt or *.rst files, include them: - '': ['*.txt', '*.rst'], - # And include any *.msg files found in the 'hello' package, too: - 'hello': ['*.msg'], + "": ["*.txt", "*.rst"], + # And include any *.msg files found in the "hello" package, too: + "hello": ["*.msg"], } ) @@ -903,15 +903,15 @@ The setuptools setup file might look like this:: from setuptools import setup, find_packages setup( ... - packages=find_packages('src'), # include all packages under src - package_dir={'':'src'}, # tell distutils packages are under src + packages=find_packages("src"), # include all packages under src + package_dir={"": "src"}, # tell distutils packages are under src package_data={ # If any package contains *.txt files, include them: - '': ['*.txt'], - # And include any *.dat files found in the 'data' subdirectory - # of the 'mypkg' package, also: - 'mypkg': ['data/*.dat'], + "": ["*.txt"], + # And include any *.dat files found in the "data" subdirectory + # of the "mypkg" package, also: + "mypkg": ["data/*.dat"], } ) @@ -926,7 +926,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 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 ``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if @@ -951,18 +951,18 @@ to do things like this:: from setuptools import setup, find_packages setup( ... - packages=find_packages('src'), # include all packages under src - package_dir={'':'src'}, # tell distutils packages are under src + packages=find_packages("src"), # include all packages under src + package_dir={"": "src"}, # tell distutils packages are under src include_package_data=True, # include everything in source control # ...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 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* from installation, even if they were listed in ``package_data`` or were included as a result of using ``include_package_data``. @@ -1096,12 +1096,12 @@ for our hypothetical blogging tool:: setup( # ... - entry_points={'blogtool.parsers': '.rst = some_module:SomeClass'} + entry_points={"blogtool.parsers": ".rst = some_module:SomeClass"} ) setup( # ... - entry_points={'blogtool.parsers': ['.rst = some_module:a_func']} + entry_points={"blogtool.parsers": [".rst = some_module:a_func"]} ) setup( @@ -1309,7 +1309,7 @@ participates in. For example, the ZopeInterface project might do this:: setup( # ... - namespace_packages=['zope'] + namespace_packages=["zope"] ) because it contains a ``zope.interface`` package that lives in the ``zope`` @@ -1327,7 +1327,7 @@ packages' ``__init__.py`` files (and the ``__init__.py`` of any parent packages), in a normal Python package layout. These ``__init__.py`` files *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 the current package is registered as a namespace package. @@ -1410,7 +1410,7 @@ pattern. So, you can use a command line like:: 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 delete any egg files from the distribution directory except for the three that were built most recently. @@ -1500,7 +1500,7 @@ To ensure Cython is available, include Cython in the build-requires section of your pyproject.toml:: [build-system] - requires=[..., 'cython'] + requires=[..., "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 @@ -2351,7 +2351,7 @@ parsing ``metadata`` and ``options`` sections into a dictionary. 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 @@ -2531,7 +2531,7 @@ a file. Here's what the writer utility looks like:: argname = os.path.splitext(basename)[0] value = getattr(cmd.distribution, argname, None) if value is not None: - value = '\n'.join(value) + '\n' + value = "\n".join(value) + "\n" cmd.write_or_delete_file(argname, filename, value) As you can see, ``egg_info.writers`` entry points must be a function taking -- cgit v1.2.3 From 310771c2808dc18218cc30082402c784deb6d2fa Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 18 Jan 2020 07:30:32 -0800 Subject: Add 'Programming Language :: Python :: 3 :: Only' trove classifier The project has been Python 3 only since a46a6bfd903ecc292fc3645c37c1b72781528095. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 18c9e1df..35db335a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,7 @@ classifiers = License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 -- cgit v1.2.3 From 9d32a834dfea6e144b05e502fca506fc19d4fa71 Mon Sep 17 00:00:00 2001 From: Ben Nuttall Date: Sun, 19 Jan 2020 12:13:12 +0000 Subject: Ensure bdist_wheel no longer creates a universal wheel, close #1976 by removing the [bdist_wheel] section --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 18c9e1df..f7dc4ea3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,9 +14,6 @@ repository = https://upload.pypi.org/legacy/ [sdist] formats = zip -[bdist_wheel] -universal = 1 - [metadata] name = setuptools version = 45.0.0 -- cgit v1.2.3 From 38af5c857fc93706cbb13de1c5e5a0b0a458fdce Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 10:54:16 -0500 Subject: Update changelog. --- changelog.d/1974.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1974.change.rst diff --git a/changelog.d/1974.change.rst b/changelog.d/1974.change.rst new file mode 100644 index 00000000..cadf1cf1 --- /dev/null +++ b/changelog.d/1974.change.rst @@ -0,0 +1 @@ +Add Python 3 Only Trove Classifier and remove universal wheel declaration for more complete transition from Python 2. -- cgit v1.2.3 From 756a7d662a076657ddf67f0cba699ca5430cb840 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 10:55:07 -0500 Subject: =?UTF-8?q?Bump=20version:=2045.0.0=20=E2=86=92=2045.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 8 ++++++++ changelog.d/1458.change.rst | 1 - changelog.d/1704.change.rst | 1 - changelog.d/1974.change.rst | 1 - setup.cfg | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) delete mode 100644 changelog.d/1458.change.rst delete mode 100644 changelog.d/1704.change.rst delete mode 100644 changelog.d/1974.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 77143907..ef8a3877 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 45.0.0 +current_version = 45.1.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 4a81e995..198854fc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,11 @@ +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 ------- diff --git a/changelog.d/1458.change.rst b/changelog.d/1458.change.rst deleted file mode 100644 index c953127a..00000000 --- a/changelog.d/1458.change.rst +++ /dev/null @@ -1 +0,0 @@ -Add minimum sunset date and preamble to Python 2 warning. diff --git a/changelog.d/1704.change.rst b/changelog.d/1704.change.rst deleted file mode 100644 index 62450835..00000000 --- a/changelog.d/1704.change.rst +++ /dev/null @@ -1 +0,0 @@ -Set sys.argv[0] in setup script run by build_meta.__legacy__ diff --git a/changelog.d/1974.change.rst b/changelog.d/1974.change.rst deleted file mode 100644 index cadf1cf1..00000000 --- a/changelog.d/1974.change.rst +++ /dev/null @@ -1 +0,0 @@ -Add Python 3 Only Trove Classifier and remove universal wheel declaration for more complete transition from Python 2. diff --git a/setup.cfg b/setup.cfg index e335ea22..bef019ee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ formats = zip [metadata] name = setuptools -version = 45.0.0 +version = 45.1.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3 From fcc9680fd931645d0e6928a358d726daa1ab220e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:15:42 -0500 Subject: Add azure pipelines from jaraco/skeleton --- azure-pipelines.yml | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..3e80bf44 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,71 @@ +# 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: 'Ubuntu-18.04' + +variables: +- group: Azure secrets + +stages: +- stage: Test + jobs: + + - job: 'Test' + strategy: + matrix: + Python36: + python.version: '3.6' + Python38: + python.version: '3.8' + 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) + displayName: 'publish to PyPI' + + condition: contains(variables['Build.SourceBranch'], 'tags') -- cgit v1.2.3 From d09dd5ff998887536e3e898ec7c007053d96e0aa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:29:30 -0500 Subject: Include PKG-INFO in minimal egg-info so that metadata doesn't need to be generated twice during bootstrap. --- bootstrap.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index 8c7d7fc3..077bf690 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -25,6 +25,7 @@ minimal_egg_info = textwrap.dedent(""" entry_points = setuptools.dist:check_entry_points [egg_info.writers] + PKG-INFO = setuptools.command.egg_info:write_pkg_info dependency_links.txt = setuptools.command.egg_info:overwrite_arg entry_points.txt = setuptools.command.egg_info:write_entries requires.txt = setuptools.command.egg_info:write_requirements @@ -52,8 +53,6 @@ def run_egg_info(): cmd = [sys.executable, 'setup.py', 'egg_info'] print("Regenerating egg_info") subprocess.check_call(cmd) - print("...and again.") - subprocess.check_call(cmd) def main(): -- cgit v1.2.3 From 7f6394863dc096b9f31e71a5843acbd836ff8d6c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:43:44 -0500 Subject: Only run 'egg_info' when bootstrapping was required. --- bootstrap.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/bootstrap.py b/bootstrap.py index 077bf690..8fa9e4b5 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -36,10 +36,11 @@ def ensure_egg_info(): if os.path.exists('setuptools.egg-info'): return 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 """ @@ -55,9 +56,4 @@ def run_egg_info(): subprocess.check_call(cmd) -def main(): - ensure_egg_info() - run_egg_info() - - -__name__ == '__main__' and main() +__name__ == '__main__' and ensure_egg_info() -- cgit v1.2.3 From 94f88bf48af78c4f961fe42241da556837efa3c1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:54:02 -0500 Subject: Bootstrap the environment in tox, allowing simple 'tox' to run tests and simplifying all of the pipelines. --- .github/workflows/python-tests.yml | 4 ---- .travis.yml | 2 -- appveyor.yml | 1 - docs/conf.py | 2 +- docs/developer-guide.txt | 8 ++------ tools/tox_pip.py | 11 +++++++++-- tox.ini | 4 +--- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 71957aba..a95a5b1d 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -52,10 +52,6 @@ jobs: 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 if: >- ! startsWith(matrix.os, 'windows-') diff --git a/.travis.yml b/.travis.yml index 263386c8..fe875ab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,8 +40,6 @@ install: - pip freeze --all - env -# update egg_info based on setup.py in checkout -- python bootstrap.py - "! grep pyc setuptools.egg-info/SOURCES.txt" script: diff --git a/appveyor.yml b/appveyor.yml index fc65a9a7..f7ab22f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,6 @@ test_script: - python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel - pip install --upgrade tox tox-venv virtualenv - pip freeze --all - - python bootstrap.py - tox -- --cov after_test: diff --git a/docs/conf.py b/docs/conf.py index cbd19fb4..6f6ae13a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ import os # hack to run the bootstrap script so that jaraco.packaging.sphinx # can invoke setup.py 'READTHEDOCS' in os.environ and subprocess.check_call( - [sys.executable, 'bootstrap.py'], + [sys.executable, '-m', 'bootstrap'], cwd=os.path.join(os.path.dirname(__file__), os.path.pardir), ) diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt index d145fba1..0b4ae4d4 100644 --- a/docs/developer-guide.txt +++ b/docs/developer-guide.txt @@ -104,12 +104,8 @@ from the command line after pushing a new branch. Testing ------- -The primary tests are run using tox. To run the tests, first create the metadata -needed to run the tests:: - - $ python bootstrap.py - -Then make sure you have tox installed, and invoke it:: +The primary tests are run using tox. Make sure you have tox installed, +and invoke it:: $ tox diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 06655fe4..2d33e9e5 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -14,13 +14,20 @@ def remove_setuptools(): subprocess.check_call(cmd, cwd='.tox') +def bootstrap(): + print("Running bootstrap") + cmd = [sys.executable, '-m', 'bootstrap'] + subprocess.check_call(cmd) + + def pip(args): # Honor requires-python when installing test suite dependencies if any('-r' in arg for arg in args): os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = '0' - # When installing '.', remove setuptools - '.' in args and remove_setuptools() + if '.' in args: + remove_setuptools() + bootstrap() cmd = [sys.executable, '-m', 'pip'] + args subprocess.check_call(cmd) diff --git a/tox.ini b/tox.ini index d458dc33..e71067be 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,3 @@ -# Note: Run "python bootstrap.py" before running Tox, to generate metadata. -# # To run Tox against all supported Python interpreters, you can set: # # export TOXENV='py27,py3{5,6,7,8},pypy,pypy3' @@ -49,7 +47,7 @@ commands=codecov -X gcov --file {toxworkdir}/coverage.xml deps = -r{toxinidir}/docs/requirements.txt skip_install=True commands = - python {toxinidir}/bootstrap.py + python -m bootstrap sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html sphinx-build -W -b man -d {envtmpdir}/doctrees docs docs/build/man -- cgit v1.2.3 From 98bf0b5d2da335aa12517cbb01bc733eee3b216b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:55:33 -0500 Subject: Remove another reference to py27 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d458dc33..9d27dd17 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # # 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] envlist=python -- cgit v1.2.3 From a0e8d0568d84e29066a5b45aade5aafe28237ec0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 11:57:02 -0500 Subject: Disable Python 2 tests on Github Actions --- .github/workflows/python-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 71957aba..fab2169a 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -18,7 +18,6 @@ jobs: - 3.7 - 3.6 - 3.5 - - 2.7 os: - ubuntu-18.04 - ubuntu-16.04 -- cgit v1.2.3 From 3d4d8b9dde61b87271861b8c7ebeb168ac4fa72b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 12:46:30 -0500 Subject: =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 39 +++++----------- pkg_resources/__init__.py | 4 +- pkg_resources/tests/test_pkg_resources.py | 8 ++-- pkg_resources/tests/test_resources.py | 3 +- setuptools/__init__.py | 6 +-- setuptools/archive_util.py | 6 ++- setuptools/build_meta.py | 24 ++++++---- setuptools/command/build_clib.py | 31 +++++++------ setuptools/command/build_ext.py | 9 ++-- setuptools/command/easy_install.py | 31 +++++++------ setuptools/command/egg_info.py | 12 +++-- setuptools/command/install_lib.py | 3 +- setuptools/command/py36compat.py | 2 +- setuptools/command/test.py | 3 +- setuptools/command/upload_docs.py | 6 +-- setuptools/dep_util.py | 4 +- setuptools/installer.py | 4 +- setuptools/msvc.py | 17 ++++--- setuptools/namespaces.py | 16 ++++--- setuptools/package_index.py | 6 ++- setuptools/py27compat.py | 2 +- setuptools/sandbox.py | 9 ++-- setuptools/site-patch.py | 10 ++-- setuptools/ssl_support.py | 23 +++++---- setuptools/tests/test_build_meta.py | 1 + setuptools/tests/test_dist.py | 11 +++-- setuptools/tests/test_easy_install.py | 77 +++++++++++++++++++------------ setuptools/tests/test_egg_info.py | 48 ++++++++++--------- setuptools/tests/test_test.py | 2 +- setuptools/tests/test_virtualenv.py | 12 +++-- setuptools/tests/test_wheel.py | 6 ++- setuptools/wheel.py | 3 +- 32 files changed, 251 insertions(+), 187 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cbd19fb4..7cc61bf7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,23 +1,3 @@ -# -*- 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 sys import os @@ -30,10 +10,8 @@ import os 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'] # Add any paths that contain templates here, relative to this directory. @@ -45,7 +23,8 @@ source_suffix = '.txt' # The master toctree document. 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'] # List of directories, relative to source directory, that shouldn't be searched @@ -55,7 +34,7 @@ exclude_trees = [] # The name of the Pygments (syntax highlighting) style to use. 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 # Sphinx are currently 'default' and 'sphinxdoc'. @@ -69,7 +48,10 @@ html_theme_path = ['_theme'] html_use_smartypants = True # 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. html_use_modindex = False @@ -77,10 +59,11 @@ html_use_modindex = False # If false, no index is generated. html_use_index = False -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output -- # 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, +# documentclass [howto/manual]). latex_documents = [ ('index', 'Setuptools.tex', 'Setuptools Documentation', 'The fellowship of the packaging', 'manual'), diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 3fa883ce..75563f95 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -2329,7 +2329,8 @@ 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(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 @@ -3288,6 +3289,7 @@ def _initialize_master_working_set(): list(map(working_set.add_entry, sys.path)) globals().update(locals()) + class PkgResourcesDeprecationWarning(Warning): """ Base class for warning about deprecations in ``pkg_resources`` diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py index 5960868a..78281869 100644 --- a/pkg_resources/tests/test_pkg_resources.py +++ b/pkg_resources/tests/test_pkg_resources.py @@ -17,7 +17,9 @@ try: except ImportError: import mock -from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution +from pkg_resources import ( + DistInfoDistribution, Distribution, EggInfoDistribution, +) from setuptools.extern import six from pkg_resources.extern.six.moves import map from pkg_resources.extern.six import text_type, string_types @@ -279,8 +281,8 @@ def make_distribution_no_version(tmpdir, basename): ('dist-info', 'METADATA', DistInfoDistribution), ], ) -def test_distribution_version_missing(tmpdir, suffix, expected_filename, - expected_dist_type): +def test_distribution_version_missing( + tmpdir, suffix, expected_filename, expected_dist_type): """ Test Distribution.version when the "Version" header is missing. """ diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py index 93fa7114..ed7cdfcc 100644 --- a/pkg_resources/tests/test_resources.py +++ b/pkg_resources/tests/test_resources.py @@ -15,7 +15,7 @@ import pkg_resources from pkg_resources import ( parse_requirements, VersionConflict, parse_version, Distribution, EntryPoint, Requirement, safe_version, safe_name, - WorkingSet, PkgResourcesDeprecationWarning) + WorkingSet) # from Python 3.6 docs. @@ -501,7 +501,6 @@ class TestEntryPoints: ep.load(require=False) - class TestRequirements: def testBasics(self): r = Requirement.parse("Twisted>=1.2") diff --git a/setuptools/__init__.py b/setuptools/__init__.py index a71b2bbd..b08c2f62 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,7 +1,6 @@ """Extensions to the 'distutils' for large or complex distributions""" import os -import sys import functools import distutils.core import distutils.filelist @@ -31,7 +30,7 @@ __all__ = [ ] if PY3: - __all__.append('find_namespace_packages') + __all__.append('find_namespace_packages') __version__ = setuptools.version.__version__ @@ -123,7 +122,7 @@ class PEP420PackageFinder(PackageFinder): find_packages = PackageFinder.find if PY3: - find_namespace_packages = PEP420PackageFinder.find + find_namespace_packages = PEP420PackageFinder.find def _install_setup_requires(attrs): @@ -144,6 +143,7 @@ def setup(**attrs): _install_setup_requires(attrs) return distutils.core.setup(**attrs) + setup.__doc__ = distutils.core.setup.__doc__ diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py index 81436044..64528ca7 100644 --- a/setuptools/archive_util.py +++ b/setuptools/archive_util.py @@ -25,7 +25,8 @@ def default_filter(src, 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): """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` @@ -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 # 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 if member.issym(): base = posixpath.dirname(member.name) diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index eb9e815e..a1c951cf 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -48,6 +48,7 @@ __all__ = ['get_requires_for_build_sdist', '__legacy__', 'SetupRequirementsError'] + class SetupRequirementsError(BaseException): def __init__(self, specifiers): self.specifiers = specifiers @@ -143,7 +144,8 @@ class _BuildMetaBackend(object): def get_requires_for_build_wheel(self, config_settings=None): config_settings = self._fix_config(config_settings) - return self._get_build_requires(config_settings, requirements=['wheel']) + return self._get_build_requires( + config_settings, requirements=['wheel']) def get_requires_for_build_sdist(self, config_settings=None): config_settings = self._fix_config(config_settings) @@ -160,8 +162,10 @@ class _BuildMetaBackend(object): dist_infos = [f for f in os.listdir(dist_info_directory) if f.endswith('.dist-info')] - if (len(dist_infos) == 0 and - len(_get_immediate_subdirectories(dist_info_directory)) == 1): + if ( + len(dist_infos) == 0 and + len(_get_immediate_subdirectories(dist_info_directory)) == 1 + ): dist_info_directory = os.path.join( dist_info_directory, os.listdir(dist_info_directory)[0]) @@ -193,7 +197,8 @@ class _BuildMetaBackend(object): config_settings["--global-option"]) 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) if os.path.exists(result_path): # os.rename will fail overwriting on non-Unix. @@ -202,7 +207,6 @@ class _BuildMetaBackend(object): return result_basename - def build_wheel(self, wheel_directory, config_settings=None, metadata_directory=None): return self._build_with_temp_dir(['bdist_wheel'], '.whl', @@ -217,9 +221,12 @@ class _BuildMetaBackend(object): class _BuildMetaLegacyBackend(_BuildMetaBackend): """Compatibility backend for setuptools - This is a version of setuptools.build_meta that endeavors to maintain backwards - compatibility with pre-PEP 517 modes of invocation. It exists as a temporary - bridge between the old packaging mechanism and the new packaging mechanism, + This is a version of setuptools.build_meta that endeavors + to maintain backwards + compatibility with pre-PEP 517 modes of invocation. It + exists as a temporary + bridge between the old packaging mechanism and the new + packaging mechanism, and will eventually be removed. """ def run_setup(self, setup_script='setup.py'): @@ -250,6 +257,7 @@ class _BuildMetaLegacyBackend(_BuildMetaBackend): sys.path[:] = sys_path sys.argv[0] = sys_argv_0 + # The primary backend _BACKEND = _BuildMetaBackend() diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index 09caff6f..88f0d095 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -71,28 +71,31 @@ class build_clib(orig.build_clib): 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 # directory. (This should probably change to putting object # files in a temporary build directory.) macros = build_info.get('macros') include_dirs = build_info.get('include_dirs') cflags = build_info.get('cflags') - objects = self.compiler.compile( - sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=include_dirs, - extra_postargs=cflags, - debug=self.debug - ) + self.compiler.compile( + sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + extra_postargs=cflags, + debug=self.debug + ) # Now "link" the object files together into a static library. # (On Unix at least, this isn't really linking -- it just # builds an archive. Whatever.) self.compiler.create_static_lib( - expected_objects, - lib_name, - output_dir=self.build_clib, - debug=self.debug - ) + expected_objects, + lib_name, + output_dir=self.build_clib, + debug=self.debug + ) diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py index 1b51e040..03b6f346 100644 --- a/setuptools/command/build_ext.py +++ b/setuptools/command/build_ext.py @@ -14,7 +14,8 @@ from setuptools.extern import six if six.PY2: import imp - EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] + EXTENSION_SUFFIXES = [ + s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION] else: from importlib.machinery import EXTENSION_SUFFIXES @@ -29,7 +30,7 @@ except ImportError: # make sure _config_vars is initialized 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): @@ -65,7 +66,9 @@ elif os.name != 'nt': except ImportError: pass -if_dl = lambda s: s if have_rtld else '' + +def if_dl(s): + return s if have_rtld else '' def get_abi3_suffix(): diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 426301d6..b95ef1f6 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -121,7 +121,8 @@ else: 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): @@ -414,8 +415,8 @@ class easy_install(Command): if show_deprecation: self.announce( "WARNING: The easy_install command is deprecated " - "and will be removed in a future version." - , log.WARN, + "and will be removed in a future version.", + log.WARN, ) if self.verbose != self.distribution.verbose: log.set_verbosity(self.verbose) @@ -507,13 +508,13 @@ class easy_install(Command): the distutils default setting) was: %s - """).lstrip() + """).lstrip() # noqa __not_exists_id = textwrap.dedent(""" This directory does not currently exist. Please create it and try again, or choose a different installation directory (using the -d or --install-dir option). - """).lstrip() + """).lstrip() # noqa __access_msg = textwrap.dedent(""" Perhaps your account does not have write access to this directory? If the @@ -529,7 +530,7 @@ class easy_install(Command): https://setuptools.readthedocs.io/en/latest/easy_install.html Please make the appropriate changes for your system and try again. - """).lstrip() + """).lstrip() # noqa def cant_write_to_target(self): msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) @@ -1093,13 +1094,13 @@ class easy_install(Command): 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 version or higher - """).lstrip() + """).lstrip() # noqa __id_warning = textwrap.dedent(""" 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 PYTHONPATH, or by being added to sys.path by your code.) - """) + """) # noqa def installation_report(self, req, dist, what="Installed"): """Helpful installation message for display to package users""" @@ -1124,7 +1125,7 @@ class easy_install(Command): %(python)s setup.py develop See the setuptools documentation for the "develop" command for more info. - """).lstrip() + """).lstrip() # noqa def report_editable(self, spec, setup_script): dirname = os.path.dirname(setup_script) @@ -1307,7 +1308,8 @@ class easy_install(Command): 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 @@ -2093,7 +2095,8 @@ class ScriptWriter: @classmethod def get_script_header(cls, script_text, executable=None, wininst=False): # for backward compatibility - warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2) + warnings.warn( + "Use get_header", EasyInstallDeprecationWarning, stacklevel=2) if wininst: executable = "python.exe" return cls.get_header(script_text, executable) @@ -2342,6 +2345,8 @@ def _patch_usage(): finally: distutils.core.gen_usage = saved + class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): - """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" - + """ + Warning for EasyInstall deprecations, bypassing suppression. + """ diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index a5c5a2fc..7fa89541 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -33,6 +33,7 @@ from setuptools.glob import glob from setuptools.extern import packaging from setuptools import SetuptoolsDeprecationWarning + def translate_pattern(glob): """ Translate a file path glob like '*.txt' in to a regular expression. @@ -113,7 +114,7 @@ def translate_pattern(glob): pat += sep pat += r'\Z' - return re.compile(pat, flags=re.MULTILINE|re.DOTALL) + return re.compile(pat, flags=re.MULTILINE | re.DOTALL) class InfoCommon: @@ -637,7 +638,9 @@ def warn_depends_obsolete(cmd, basename, filename): def _write_requirements(stream, reqs): lines = yield_lines(reqs or ()) - append_cr = lambda line: line + '\n' + + def append_cr(line): + return line + '\n' lines = map(append_cr, lines) stream.writelines(lines) @@ -703,7 +706,8 @@ 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.", EggInfoDeprecationWarning) + 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: @@ -714,4 +718,4 @@ def get_pkg_info_revision(): 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.""" diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index 07d65933..2e9d8757 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -77,7 +77,8 @@ class install_lib(orig.install_lib): if not hasattr(sys, 'implementation'): 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 + '.pyo' yield base + '.opt-1.pyc' diff --git a/setuptools/command/py36compat.py b/setuptools/command/py36compat.py index 61063e75..28860558 100644 --- a/setuptools/command/py36compat.py +++ b/setuptools/command/py36compat.py @@ -132,5 +132,5 @@ class sdist_add_defaults: if hasattr(sdist.sdist, '_add_defaults_standards'): # disable the functionality already available upstream - class sdist_add_defaults: + class sdist_add_defaults: # noqa pass diff --git a/setuptools/command/test.py b/setuptools/command/test.py index f6470e9c..2d83967d 100644 --- a/setuptools/command/test.py +++ b/setuptools/command/test.py @@ -129,7 +129,8 @@ class test(Command): @contextlib.contextmanager 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 we run 2to3 we can not do this inplace: diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 130a0cb6..0351da77 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -127,8 +127,8 @@ class upload_docs(upload): """ Build up the MIME payload for the POST data """ - boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\n--' + boundary + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\n--' + boundary.encode('ascii') end_boundary = sep_boundary + b'--' end_items = end_boundary, b"\n", builder = functools.partial( @@ -138,7 +138,7 @@ class upload_docs(upload): part_groups = map(builder, data.items()) parts = itertools.chain.from_iterable(part_groups) 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 def upload_file(self, filename): diff --git a/setuptools/dep_util.py b/setuptools/dep_util.py index 2931c13e..521eb716 100644 --- a/setuptools/dep_util.py +++ b/setuptools/dep_util.py @@ -1,5 +1,6 @@ from distutils.dep_util import newer_group + # yes, this is was almost entirely copy-pasted from # 'newer_pairwise()', this is just another convenience # function. @@ -10,7 +11,8 @@ def newer_pairwise_group(sources_groups, targets): of 'newer_group()'. """ 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 n_sources = [] diff --git a/setuptools/installer.py b/setuptools/installer.py index 9f8be2ef..1f183bd9 100644 --- a/setuptools/installer.py +++ b/setuptools/installer.py @@ -64,8 +64,8 @@ def fetch_build_egg(dist, req): dist.announce( 'WARNING: The pip package is not available, falling back ' 'to EasyInstall for handling setup_requires/test_requires; ' - 'this is deprecated and will be removed in a future version.' - , log.WARN + 'this is deprecated and will be removed in a future version.', + log.WARN ) return _legacy_fetch_build_egg(dist, req) # Warn if wheel is not. diff --git a/setuptools/msvc.py b/setuptools/msvc.py index 2ffe1c81..fa88c4e8 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -1225,7 +1225,7 @@ class EnvironmentInfo: arch_subdir = self.pi.target_dir(x64=True) lib = join(self.si.WindowsSdkDir, 'lib') libver = self._sdk_subdir - return [join(lib, '%sum%s' % (libver , arch_subdir))] + return [join(lib, '%sum%s' % (libver, arch_subdir))] @property def OSIncludes(self): @@ -1274,13 +1274,16 @@ class EnvironmentInfo: libpath += [ ref, 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.Networking.Connectivity.WwanContract', - '1.0.0.0'), - join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', - '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', - 'neutral'), + join( + ref, 'Windows.Networking.Connectivity.WwanContract', + '1.0.0.0'), + join( + self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs', + '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration', + 'neutral'), ] return libpath diff --git a/setuptools/namespaces.py b/setuptools/namespaces.py index dc16106d..5f403c96 100644 --- a/setuptools/namespaces.py +++ b/setuptools/namespaces.py @@ -47,13 +47,17 @@ class Installer: "p = os.path.join(%(root)s, *%(pth)r)", "importlib = has_mfs and __import__('importlib.util')", "has_mfs and __import__('importlib.machinery')", - "m = has_mfs and " + ( + "m = has_mfs and " "sys.modules.setdefault(%(pkg)r, " - "importlib.util.module_from_spec(" - "importlib.machinery.PathFinder.find_spec(%(pkg)r, " - "[os.path.dirname(p)])))", - "m = m or " - "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))", + "importlib.util.module_from_spec(" + "importlib.machinery.PathFinder.find_spec(%(pkg)r, " + "[os.path.dirname(p)])))" + ), + ( + "m = m or " + "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))" + ), "mp = (m or []) and m.__dict__.setdefault('__path__',[])", "(p not in mp) and mp.append(p)", ) diff --git a/setuptools/package_index.py b/setuptools/package_index.py index f419d471..82eb4516 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -46,7 +46,8 @@ __all__ = [ _SOCKET_TIMEOUT = 15 _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): @@ -1092,7 +1093,8 @@ def open_with_auth(url, opener=urllib.request.urlopen): # copy of urllib.parse._splituser from Python 3.8 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('@') return (user if delim else None), host diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py index 1d57360f..ba39af52 100644 --- a/setuptools/py27compat.py +++ b/setuptools/py27compat.py @@ -16,7 +16,7 @@ def get_all_headers(message, key): if six.PY2: - def get_all_headers(message, key): + def get_all_headers(message, key): # noqa return message.getheaders(key) diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py index 685f3f72..e46dfc8d 100644 --- a/setuptools/sandbox.py +++ b/setuptools/sandbox.py @@ -13,6 +13,8 @@ from setuptools.extern import six from setuptools.extern.six.moves import builtins, map import pkg_resources.py31compat +from distutils.errors import DistutilsError +from pkg_resources import working_set if sys.platform.startswith('java'): import org.python.modules.posix.PosixModule as _os @@ -23,8 +25,6 @@ try: except NameError: _file = None _open = open -from distutils.errors import DistutilsError -from pkg_resources import working_set __all__ = [ @@ -374,7 +374,7 @@ class AbstractSandbox: if hasattr(os, 'devnull'): - _EXCEPTIONS = [os.devnull,] + _EXCEPTIONS = [os.devnull] else: _EXCEPTIONS = [] @@ -466,7 +466,8 @@ class DirectorySandbox(AbstractSandbox): 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()] ) diff --git a/setuptools/site-patch.py b/setuptools/site-patch.py index 40b00de0..be0d43d3 100644 --- a/setuptools/site-patch.py +++ b/setuptools/site-patch.py @@ -38,22 +38,24 @@ def __boot(): else: 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 sys.__egginsert = 0 # and reset the current one for item in PYTHONPATH: - addsitedir(item) + addsitedir(item) # noqa sys.__egginsert += oldpos # restore effective old position - d, nd = makepath(stdpath[0]) + d, nd = makepath(stdpath[0]) # noqa insert_at = None new_path = [] for item in sys.path: - p, np = makepath(item) + p, np = makepath(item) # noqa if np == nd and insert_at is None: # We've hit the first 'system' path entry, so added entries go here diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py index 226db694..17c14c46 100644 --- a/setuptools/ssl_support.py +++ b/setuptools/ssl_support.py @@ -35,7 +35,8 @@ try: except AttributeError: 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: @@ -85,8 +86,10 @@ if not match_hostname: return dn.lower() == hostname.lower() # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in which - # the wildcard character comprises a label other than the left-most label. + # The client SHOULD NOT attempt to match a + # presented identifier in which the wildcard + # character comprises a label other than the + # left-most label. if leftmost == '*': # When '*' is a fragment by itself, it matches a non-empty dotless # fragment. @@ -137,15 +140,16 @@ if not match_hostname: return dnsnames.append(value) if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" + raise CertificateError( + "hostname %r doesn't match either of %s" % (hostname, ', '.join(map(repr, dnsnames)))) elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" + raise CertificateError( + "hostname %r doesn't match %r" % (hostname, dnsnames[0])) else: - raise CertificateError("no appropriate commonName or " + raise CertificateError( + "no appropriate commonName or " "subjectAltName fields were found") @@ -158,7 +162,8 @@ class VerifyingHTTPSHandler(HTTPSHandler): def https_open(self, req): return self.do_open( - lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req + lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), + req ) diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index d68444f6..8fcf3055 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -23,6 +23,7 @@ class BuildBackendBase: self.env = env self.backend_name = backend_name + class BuildBackend(BuildBackendBase): """PEP 517 Build Backend""" diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index 36237f24..b93ef148 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -284,7 +284,7 @@ def test_provides_extras_deterministic_order(): dist = Distribution(attrs) assert dist.metadata.provides_extras == ['b', 'a'] - + CHECK_PACKAGE_DATA_TESTS = ( # Valid. ({ @@ -309,7 +309,8 @@ CHECK_PACKAGE_DATA_TESTS = ( ({ '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) ({ @@ -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): if expected_message is None: assert check_package_data(None, 'package_data', package_data) is None else: - with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)): + with pytest.raises( + DistutilsSetupError, match=re.escape(expected_message)): check_package_data(None, str('package_data'), package_data) diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 2be1be47..30e79fec 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -671,8 +671,10 @@ class TestSetupRequires: dep_url = path_to_url(dep_sdist, authority='localhost') test_pkg = create_setup_requires_package( temp_dir, - 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). - setup_attrs=dict(setup_requires='dependency @ %s' % dep_url)) + # Ignored (overriden by setup_attrs) + 'python-xlib', '0.19', + setup_attrs=dict( + setup_requires='dependency @ %s' % dep_url)) test_setup_py = os.path.join(test_pkg, 'setup.py') run_setup(test_setup_py, [str('--version')]) assert len(mock_index.requests) == 0 @@ -710,11 +712,14 @@ class TestSetupRequires: dep_1_0_sdist = 'dep-1.0.tar.gz' dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist)) dep_1_0_python_requires = '>=2.7' - make_python_requires_sdist(str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) + make_python_requires_sdist( + str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires) dep_2_0_sdist = 'dep-2.0.tar.gz' dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist)) - dep_2_0_python_requires = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*' - make_python_requires_sdist(str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) + dep_2_0_python_requires = '!=' + '.'.join( + map(str, sys.version_info[:2])) + '.*' + make_python_requires_sdist( + str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires) index = tmpdir / 'index.html' index.write_text(DALS( ''' @@ -726,7 +731,7 @@ class TestSetupRequires: {dep_2_0_sdist}
- ''').format( + ''').format( # noqa dep_1_0_url=dep_1_0_url, dep_1_0_sdist=dep_1_0_sdist, dep_1_0_python_requires=dep_1_0_python_requires, @@ -738,23 +743,29 @@ class TestSetupRequires: with contexts.save_pkg_resources_state(): test_pkg = create_setup_requires_package( str(tmpdir), - 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). - setup_attrs=dict(setup_requires='dep', dependency_links=[index_url])) + 'python-xlib', '0.19', # Ignored (overriden by setup_attrs). + setup_attrs=dict( + setup_requires='dep', dependency_links=[index_url])) test_setup_py = os.path.join(test_pkg, 'setup.py') run_setup(test_setup_py, [str('--version')]) - eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs')))) + eggs = list(map(str, pkg_resources.find_distributions( + os.path.join(test_pkg, '.eggs')))) assert eggs == ['dep 1.0'] - @pytest.mark.parametrize('use_legacy_installer,with_dependency_links_in_setup_py', - itertools.product((False, True), (False, True))) - def test_setup_requires_with_find_links_in_setup_cfg(self, monkeypatch, - use_legacy_installer, - with_dependency_links_in_setup_py): + @pytest.mark.parametrize( + 'use_legacy_installer,with_dependency_links_in_setup_py', + itertools.product((False, True), (False, True))) + def test_setup_requires_with_find_links_in_setup_cfg( + self, monkeypatch, use_legacy_installer, + with_dependency_links_in_setup_py): monkeypatch.setenv(str('PIP_RETRIES'), str('0')) monkeypatch.setenv(str('PIP_TIMEOUT'), str('0')) with contexts.save_pkg_resources_state(): with contexts.tempdir() as temp_dir: - make_trivial_sdist(os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42') + make_trivial_sdist( + os.path.join(temp_dir, 'python-xlib-42.tar.gz'), + 'python-xlib', + '42') test_pkg = os.path.join(temp_dir, 'test_pkg') test_setup_py = os.path.join(test_pkg, 'setup.py') test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') @@ -771,7 +782,7 @@ class TestSetupRequires: installer.fetch_build_egg = installer._legacy_fetch_build_egg setup(setup_requires='python-xlib==42', dependency_links={dependency_links!r}) - ''').format(use_legacy_installer=use_legacy_installer, + ''').format(use_legacy_installer=use_legacy_installer, # noqa dependency_links=dependency_links)) with open(test_setup_cfg, 'w') as fp: fp.write(DALS( @@ -783,14 +794,17 @@ class TestSetupRequires: find_links=temp_dir)) 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 # an already installed `dep[extra]`, which in turn depends # on `extra_dep` (whose is not already installed). with contexts.save_pkg_resources_state(): with contexts.tempdir() as temp_dir: # Create source distribution for `extra_dep`. - make_trivial_sdist(os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0') + make_trivial_sdist( + os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), + 'extra_dep', '1.0') # Create source tree for `dep`. dep_pkg = os.path.join(temp_dir, 'dep') os.mkdir(dep_pkg) @@ -806,12 +820,12 @@ class TestSetupRequires: 'setup.cfg': '', }, prefix=dep_pkg) # "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) # Create source tree for test package. test_pkg = os.path.join(temp_dir, 'test_pkg') test_setup_py = os.path.join(test_pkg, 'setup.py') - test_setup_cfg = os.path.join(test_pkg, 'setup.cfg') os.mkdir(test_pkg) with open(test_setup_py, 'w') as fp: fp.write(DALS( @@ -881,16 +895,19 @@ def make_nspkg_sdist(dist_path, distname, version): def make_python_requires_sdist(dist_path, distname, version, python_requires): make_sdist(dist_path, [ - ('setup.py', DALS("""\ - import setuptools - setuptools.setup( - name={name!r}, - version={version!r}, - python_requires={python_requires!r}, - ) - """).format(name=distname, version=version, - python_requires=python_requires)), - ('setup.cfg', ''), + ( + 'setup.py', + DALS("""\ + import setuptools + setuptools.setup( + name={name!r}, + version={version!r}, + python_requires={python_requires!r}, + ) + """).format( + name=distname, version=version, + python_requires=python_requires)), + ('setup.cfg', ''), ]) diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py index 0db204ba..109f9135 100644 --- a/setuptools/tests/test_egg_info.py +++ b/setuptools/tests/test_egg_info.py @@ -525,19 +525,19 @@ class TestEggInfo: license_file = LICENSE """), 'LICENSE': "Test license" - }, True), # with license + }, True), # with license ({ 'setup.cfg': DALS(""" [metadata] license_file = INVALID_LICENSE """), 'LICENSE': "Test license" - }, False), # with an invalid license + }, False), # with an invalid license ({ 'setup.cfg': DALS(""" """), 'LICENSE': "Test license" - }, False), # no license_file attribute + }, False), # no license_file attribute ({ 'setup.cfg': DALS(""" [metadata] @@ -545,7 +545,7 @@ class TestEggInfo: """), 'MANIFEST.in': "exclude LICENSE", 'LICENSE': "Test license" - }, False) # license file is manually excluded + }, False) # license file is manually excluded ]) def test_setup_cfg_license_file( self, tmpdir_cwd, env, files, license_in_sources): @@ -565,7 +565,8 @@ class TestEggInfo: assert 'LICENSE' in sources_text else: 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", [ ({ @@ -577,7 +578,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses + }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses ({ 'setup.cfg': DALS(""" [metadata] @@ -585,7 +586,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas + }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas ({ 'setup.cfg': DALS(""" [metadata] @@ -594,7 +595,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license + }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license ({ 'setup.cfg': DALS(""" [metadata] @@ -602,7 +603,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty + }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty ({ 'setup.cfg': DALS(""" [metadata] @@ -610,7 +611,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line + }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line ({ 'setup.cfg': DALS(""" [metadata] @@ -619,12 +620,12 @@ class TestEggInfo: INVALID_LICENSE """), 'LICENSE-ABC': "Test license" - }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license + }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license ({ 'setup.cfg': DALS(""" """), 'LICENSE': "Test license" - }, [], ['LICENSE']), # no license_files attribute + }, [], ['LICENSE']), # no license_files attribute ({ 'setup.cfg': DALS(""" [metadata] @@ -632,7 +633,7 @@ class TestEggInfo: """), 'MANIFEST.in': "exclude LICENSE", 'LICENSE': "Test license" - }, [], ['LICENSE']), # license file is manually excluded + }, [], ['LICENSE']), # license file is manually excluded ({ 'setup.cfg': DALS(""" [metadata] @@ -643,7 +644,7 @@ class TestEggInfo: 'MANIFEST.in': "exclude LICENSE-XYZ", 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded + }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded ]) def test_setup_cfg_license_files( self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): @@ -674,7 +675,7 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC license", 'LICENSE-XYZ': "XYZ license" - }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty + }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty ({ 'setup.cfg': DALS(""" [metadata] @@ -684,7 +685,8 @@ class TestEggInfo: """), 'LICENSE-ABC': "ABC 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(""" [metadata] @@ -696,7 +698,7 @@ class TestEggInfo: 'LICENSE-ABC': "ABC license", 'LICENSE-PQR': "PQR license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined + }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined ({ 'setup.cfg': DALS(""" [metadata] @@ -709,7 +711,8 @@ class TestEggInfo: 'LICENSE-ABC': "ABC license", 'LICENSE-PQR': "PQR license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license + # duplicate license + }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), ({ 'setup.cfg': DALS(""" [metadata] @@ -720,7 +723,8 @@ class TestEggInfo: 'LICENSE-ABC': "ABC license", 'LICENSE-PQR': "PQR license", 'LICENSE-XYZ': "XYZ license" - }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset + # combined subset + }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), ({ 'setup.cfg': DALS(""" [metadata] @@ -730,7 +734,8 @@ class TestEggInfo: LICENSE-PQR """), '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(""" [metadata] @@ -743,7 +748,8 @@ class TestEggInfo: 'LICENSE-ABC': "ABC license", 'LICENSE-PQR': "PQR 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( self, tmpdir_cwd, env, files, incl_licenses, excl_licenses): diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py index 6242a018..8ee70a7e 100644 --- a/setuptools/tests/test_test.py +++ b/setuptools/tests/test_test.py @@ -12,7 +12,7 @@ from setuptools.command.test import test from setuptools.dist import Distribution from .textwrap import DALS -from . import contexts + SETUP_PY = DALS(""" from setuptools import setup diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 2c35825a..b009fbd6 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -8,8 +8,6 @@ from pytest_fixture_config import yield_requires_config import pytest_virtualenv -from setuptools.extern import six - from .textwrap import DALS from .test_easy_install import make_nspkg_sdist @@ -64,7 +62,7 @@ def _get_pip_versions(): from urllib.request import urlopen from urllib.error import URLError except ImportError: - from urllib2 import urlopen, URLError # Python 2.7 compat + from urllib2 import urlopen, URLError # Python 2.7 compat try: urlopen('https://pypi.org', timeout=1) @@ -180,12 +178,16 @@ def _check_test_command_install_requirements(virtualenv, tmpdir): )).format(tmpdir=tmpdir)) assert tmpdir.join('success').check() + def test_test_command_install_requirements(virtualenv, tmpdir): # Ensure pip/wheel packages are installed. - virtualenv.run("python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") + virtualenv.run( + "python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") _check_test_command_install_requirements(virtualenv, tmpdir) -def test_test_command_install_requirements_when_using_easy_install(bare_virtualenv, tmpdir): + +def test_test_command_install_requirements_when_using_easy_install( + bare_virtualenv, tmpdir): _check_test_command_install_requirements(bare_virtualenv, tmpdir) diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index 55d346c6..39eb06ee 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -455,7 +455,8 @@ WHEEL_INSTALL_TESTS = ( id='empty_namespace_package', file_defs={ 'foobar': { - '__init__.py': "__import__('pkg_resources').declare_namespace(__name__)", + '__init__.py': + "__import__('pkg_resources').declare_namespace(__name__)", }, }, setup_kwargs=dict( @@ -579,4 +580,5 @@ def test_wheel_is_compatible(monkeypatch): for t in parse_tag('cp36-cp36m-manylinux1_x86_64'): yield t monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags) - assert Wheel('onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible() + assert Wheel( + 'onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible() diff --git a/setuptools/wheel.py b/setuptools/wheel.py index 025aaa82..ec1106a7 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -77,7 +77,8 @@ class Wheel: def is_compatible(self): '''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) def egg_name(self): -- cgit v1.2.3 From 313ac58f51c6ef92170647c4cc8626043f68a26b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 13:19:31 -0500 Subject: Run flake8 tests as part of test suite. --- .flake8 | 12 ++++++++++++ pytest.ini | 7 ++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..c6580616 --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +[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 diff --git a/pytest.ini b/pytest.ini index 0bc1ec01..904fe336 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,9 +1,6 @@ [pytest] -addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX -norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .* -flake8-ignore = - setuptools/site-patch.py F821 - setuptools/py*compat.py F811 +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 .* setuptools/_vendor pkg_resources/_vendor doctest_optionflags=ELLIPSIS ALLOW_UNICODE filterwarnings = # https://github.com/pypa/setuptools/issues/1823 -- cgit v1.2.3 From 5ce9e5f343ca14f9875106f37f16ad498b294183 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 13:25:45 -0500 Subject: =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/conf.py | 8 ++++---- setuptools/__init__.py | 4 ++-- setuptools/command/build_clib.py | 30 +++++++++++++++--------------- setuptools/dist.py | 2 +- setuptools/msvc.py | 2 +- setuptools/tests/__init__.py | 2 +- setuptools/tests/test_build_clib.py | 3 +-- setuptools/tests/test_config.py | 6 +++--- setuptools/tests/test_easy_install.py | 26 +++++++++++++------------- setuptools/tests/test_setuptools.py | 8 ++++---- setuptools/tests/test_wheel.py | 11 ++++++----- 11 files changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7cc61bf7..918ca034 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,10 +64,10 @@ html_use_index = False # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, # documentclass [howto/manual]). -latex_documents = [ - ('index', 'Setuptools.tex', 'Setuptools Documentation', - 'The fellowship of the packaging', 'manual'), -] +latex_documents = [( + 'index', 'Setuptools.tex', 'Setuptools Documentation', + 'The fellowship of the packaging', 'manual', +)] link_files = { '../CHANGES.rst': dict( diff --git a/setuptools/__init__.py b/setuptools/__init__.py index b08c2f62..07d6b6fa 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -191,8 +191,8 @@ class Command(_Command): ok = False if not ok: raise DistutilsOptionError( - "'%s' must be a list of strings (got %r)" - % (option, val)) + "'%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) diff --git a/setuptools/command/build_clib.py b/setuptools/command/build_clib.py index 88f0d095..67ce2444 100644 --- a/setuptools/command/build_clib.py +++ b/setuptools/command/build_clib.py @@ -25,9 +25,9 @@ class build_clib(orig.build_clib): sources = build_info.get('sources') if sources is None or not isinstance(sources, (list, tuple)): raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % lib_name) + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) sources = list(sources) log.info("building '%s' library", lib_name) @@ -38,9 +38,9 @@ class build_clib(orig.build_clib): obj_deps = build_info.get('obj_deps', dict()) if not isinstance(obj_deps, dict): raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) dependencies = [] # Get the global dependencies that are specified by the '' key. @@ -48,9 +48,9 @@ class build_clib(orig.build_clib): global_deps = obj_deps.get('', list()) if not isinstance(global_deps, (list, tuple)): raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) # Build the list to be used by newer_pairwise_group # each source will be auto-added to its dependencies. @@ -60,16 +60,16 @@ class build_clib(orig.build_clib): extra_deps = obj_deps.get(source, list()) if not isinstance(extra_deps, (list, tuple)): raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) src_deps.extend(extra_deps) dependencies.append(src_deps) expected_objects = self.compiler.object_filenames( - sources, - output_dir=self.build_temp - ) + sources, + output_dir=self.build_temp, + ) if ( newer_pairwise_group(dependencies, expected_objects) diff --git a/setuptools/dist.py b/setuptools/dist.py index fe5adf46..ad54839b 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -162,7 +162,7 @@ def write_pkg_file(self, file): if self.download_url: write_field('Download-URL', self.download_url) for project_url in self.project_urls.items(): - write_field('Project-URL', '%s, %s' % project_url) + write_field('Project-URL', '%s, %s' % project_url) long_desc = rfc822_escape(self.get_long_description()) write_field('Description', long_desc) diff --git a/setuptools/msvc.py b/setuptools/msvc.py index fa88c4e8..c2cbd1e5 100644 --- a/setuptools/msvc.py +++ b/setuptools/msvc.py @@ -544,7 +544,7 @@ class SystemInfo: # Except for VS15+, VC version is aligned with VS version self.vs_ver = self.vc_ver = ( - vc_ver or self._find_latest_available_vs_ver()) + vc_ver or self._find_latest_available_vs_ver()) def _find_latest_available_vs_ver(self): """ diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index 5f4a1c29..9c77b51f 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -6,7 +6,7 @@ from setuptools.extern.six import PY2, PY3 __all__ = [ - 'fail_on_ascii', 'py2_only', 'py3_only' + 'fail_on_ascii', 'py2_only', 'py3_only' ] diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py index 3779e679..48bea2b4 100644 --- a/setuptools/tests/test_build_clib.py +++ b/setuptools/tests/test_build_clib.py @@ -8,8 +8,7 @@ from setuptools.dist import Distribution class TestBuildCLib: @mock.patch( - 'setuptools.command.build_clib.newer_pairwise_group' - ) + 'setuptools.command.build_clib.newer_pairwise_group') def test_build_libraries(self, mock_newer): dist = Distribution() cmd = build_clib(dist) diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index 69d8d00d..2fa0b374 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -695,7 +695,7 @@ class TestOptions: ) with get_dist(tmpdir) as dist: assert set(dist.packages) == set( - ['fake_package', 'fake_package.sub_two']) + ['fake_package', 'fake_package.sub_two']) @py2_only def test_find_namespace_directive_fails_on_py2(self, tmpdir): @@ -748,7 +748,7 @@ class TestOptions: ) with get_dist(tmpdir) as dist: assert set(dist.packages) == { - 'fake_package', 'fake_package.sub_two' + 'fake_package', 'fake_package.sub_two' } def test_extras_require(self, tmpdir): @@ -881,7 +881,7 @@ class TestExternalSetters: return None @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 dist = Distribution(attrs={ diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 30e79fec..534392b9 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -629,7 +629,7 @@ class TestSetupRequires: test_pkg = create_setup_requires_package( temp_dir, setup_attrs=dict(version='attr: foobar.version'), 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') with contexts.quiet() as (stdout, stderr): @@ -905,8 +905,8 @@ def make_python_requires_sdist(dist_path, distname, version, python_requires): python_requires={python_requires!r}, ) """).format( - name=distname, version=version, - python_requires=python_requires)), + name=distname, version=version, + python_requires=python_requires)), ('setup.cfg', ''), ]) @@ -965,16 +965,16 @@ def create_setup_requires_package(path, distname='foobar', version='0.1', value = ';'.join(value) section.append('%s: %s' % (name, value)) test_setup_cfg_contents = DALS( - """ - [metadata] - {metadata} - [options] - {options} - """ - ).format( - options='\n'.join(options), - metadata='\n'.join(metadata), - ) + """ + [metadata] + {metadata} + [options] + {options} + """ + ).format( + options='\n'.join(options), + metadata='\n'.join(metadata), + ) else: test_setup_cfg_contents = '' with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f: diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 5896a69a..0da19b0e 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -223,10 +223,10 @@ class TestFeatures: py_modules=['bar_et'], remove=['bar.ext'], ), 'baz': Feature( - "baz", optional=False, packages=['pkg.baz'], - scripts=['scripts/baz_it'], - libraries=[('libfoo', 'foo/foofoo.c')] - ), + "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'], diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index 39eb06ee..f72ccbbf 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -125,11 +125,12 @@ def flatten_tree(tree): def format_install_tree(tree): - return {x.format( - py_version=PY_MAJOR, - platform=get_platform(), - shlib_ext=get_config_var('EXT_SUFFIX') or get_config_var('SO')) - for x in tree} + return { + x.format( + py_version=PY_MAJOR, + platform=get_platform(), + 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, -- cgit v1.2.3 From 7cfeee80514bbdcd91b5dfbb937a9848ba4b7cf3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 14:42:56 -0500 Subject: Extract 'find_spec' function to consolidate behavior. Ref #1905. --- setuptools/_imp.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/setuptools/_imp.py b/setuptools/_imp.py index 6ccec579..451e45a8 100644 --- a/setuptools/_imp.py +++ b/setuptools/_imp.py @@ -17,12 +17,18 @@ C_BUILTIN = 6 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): """Just like 'imp.find_module()', but with package support""" - if isinstance(paths, list): - spec = importlib.machinery.PathFinder().find_spec(module, paths) - else: - spec = importlib.util.find_spec(module, paths) + spec = find_spec(module, paths) if spec is None: raise ImportError("Can't find %s" % module) if not spec.has_location and hasattr(spec, 'submodule_search_locations'): @@ -63,20 +69,14 @@ def find_module(module, paths=None): def get_frozen_object(module, paths=None): - if isinstance(paths, list): - spec = importlib.machinery.PathFinder().find_spec(module, paths) - else: - spec = importlib.util.find_spec(module, paths) + spec = find_spec(module, paths) if not spec: raise ImportError("Can't find %s" % module) return spec.loader.get_code(module) def get_module(module, paths, info): - if isinstance(paths, list): - spec = importlib.machinery.PathFinder().find_spec(module, paths) - else: - spec = importlib.util.find_spec(module, paths) + spec = find_spec(module, paths) if not spec: raise ImportError("Can't find %s" % module) return module_from_spec(spec) -- cgit v1.2.3 From 20992988b5aec97062de8138b3c3737a38139b30 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 14:50:51 -0500 Subject: Add changelog entry. Ref #1905. --- changelog.d/1905.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1905.change.rst diff --git a/changelog.d/1905.change.rst b/changelog.d/1905.change.rst new file mode 100644 index 00000000..7b73898c --- /dev/null +++ b/changelog.d/1905.change.rst @@ -0,0 +1 @@ +Fixed test failure introduced in 41.6.0 when the 'tests' directory is not present. -- cgit v1.2.3 From 6b2490cd12e7e520b807ac35a084afd7bf3d912d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 14:55:21 -0500 Subject: Separate test for 'is_present' into its own test and add a TODO about this behavior being apparently unused. --- setuptools/tests/test_setuptools.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 0da19b0e..bca69c30 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -108,6 +108,11 @@ class TestDepends: assert not req.is_present() 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") assert req.format is None assert req.attribute is None -- cgit v1.2.3 From 7cd8b4966a6e7186ff45fe1f1c09a58f8a678113 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 15:05:08 -0500 Subject: =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins=20(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setuptools/tests/test_dist.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py index b93ef148..6e8c45fd 100644 --- a/setuptools/tests/test_dist.py +++ b/setuptools/tests/test_dist.py @@ -61,7 +61,8 @@ def test_dist_fetch_build_egg(tmpdir): dist.fetch_build_egg(r) 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(): -- cgit v1.2.3 From 6d4e23882a5b1e1f31fb452aaad9d19cf0d02604 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 14:06:35 -0500 Subject: Move test dependencies to package metadata. --- setup.cfg | 13 +++++++++++++ tests/requirements.txt | 12 ------------ tools/tox_pip.py | 20 +++++++++++++++++++- tox.ini | 3 ++- 4 files changed, 34 insertions(+), 14 deletions(-) delete mode 100644 tests/requirements.txt diff --git a/setup.cfg b/setup.cfg index bef019ee..87c25a62 100644 --- a/setup.cfg +++ b/setup.cfg @@ -57,3 +57,16 @@ ssl = wincertstore==0.2; sys_platform=='win32' certs = 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. diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index 19bf5aef..00000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -mock -pytest-flake8 -flake8-2020; python_version>="3.6" -virtualenv>=13.0.0 -pytest-virtualenv>=1.2.7 -pytest>=3.7 -wheel -coverage>=4.5.1 -pytest-cov>=2.5.1 -paver; python_version>="3.6" -futures; python_version=="2.7" -pip>=19.1 # For proper file:// URLs support. diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 2d33e9e5..1b7eeda5 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -1,6 +1,7 @@ import os import subprocess import sys +import re def remove_setuptools(): @@ -20,12 +21,29 @@ def bootstrap(): subprocess.check_call(cmd) +def is_install_self(args): + """ + Do the args represent an install of .? + """ + def strip_extras(arg): + match = re.match(r'(.*)?\[.*\]$', arg) + return match.group(1) if match else arg + + return ( + 'install' in args + and any( + arg in ['.', os.getcwd()] + for arg in map(strip_extras, args) + ) + ) + + def pip(args): # Honor requires-python when installing test suite dependencies if any('-r' in arg for arg in args): os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = '0' - if '.' in args: + if is_install_self(args): remove_setuptools() bootstrap() diff --git a/tox.ini b/tox.ini index a70dff87..aeae13ce 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,6 @@ requires = pip = python {toxinidir}/tools/tox_pip.py [testenv] -deps=-r{toxinidir}/tests/requirements.txt pip_version = pip install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all @@ -25,6 +24,8 @@ setenv = passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED commands=pytest --cov-config={toxinidir}/tox.ini --cov-report= {posargs} usedevelop=True +extras = + tests [testenv:coverage] -- cgit v1.2.3 From 548adff948383937d94ca823ca6c1367ec331b62 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 12:45:18 +0100 Subject: Use `python-version` in setup-python action Before this change, a deprecated `version` arg was used. --- .github/workflows/python-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 8941b359..a1e3d95b 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -32,7 +32,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: - version: ${{ matrix.python-version }} + python-version: ${{ matrix.python-version }} - name: Upgrade pip/setuptools/wheel run: >- python -- cgit v1.2.3 From 5a5adf2df3ff231092a78df26cea965a41a4f47f Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 12:46:15 +0100 Subject: Bump setup-python action to v1.1.1 --- .github/workflows/python-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index a1e3d95b..0801767f 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -30,7 +30,7 @@ jobs: steps: - uses: actions/checkout@master - 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: Upgrade pip/setuptools/wheel -- cgit v1.2.3 From 69ef612202e8ca5996865f23d9a4c935229b652b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 12:51:20 +0100 Subject: Cache Pip dists in GH Actions CI/CD workflows --- .github/workflows/python-tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 0801767f..64d4a0d6 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -33,6 +33,14 @@ jobs: uses: actions/setup-python@v1.1.1 with: python-version: ${{ matrix.python-version }} + - name: Cache pip + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}-${{ hashFiles('tests/requirements.txt') }}-${{ hashFiles('tox.ini') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- - name: Upgrade pip/setuptools/wheel run: >- python -- cgit v1.2.3 From 76754734f3f9dfc0817b3829c36dd6dca61c926c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 13:10:19 +0100 Subject: Switch to using the latest Ubuntu --- .github/workflows/python-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 64d4a0d6..ebfe6b18 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -19,7 +19,7 @@ jobs: - 3.6 - 3.5 os: - - ubuntu-18.04 + - ubuntu-latest - ubuntu-16.04 - macOS-latest # - windows-2019 -- cgit v1.2.3 From 5a30a9c44c2027331370cdf388a29d3de28790c2 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 14:12:52 +0100 Subject: Disable tox's parallel spinner --- .github/workflows/python-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index ebfe6b18..12ef645b 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -27,6 +27,9 @@ jobs: env: - TOXENV: python + env: + TOX_PARALLEL_NO_SPINNER: 1 + steps: - uses: actions/checkout@master - name: Set up Python ${{ matrix.python-version }} -- cgit v1.2.3 From 347f319bb642af203301cc93d4c3591bbaac635c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 14:16:37 +0100 Subject: Log Python version in CI --- .github/workflows/python-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 12ef645b..f5437b5e 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -36,6 +36,9 @@ jobs: uses: actions/setup-python@v1.1.1 with: python-version: ${{ matrix.python-version }} + - name: Log Python version + run: >- + python --version - name: Cache pip uses: actions/cache@v1 with: -- cgit v1.2.3 From 1b0d1f65a956cee5cdf3236778fc051c962a678e Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 14:36:09 +0100 Subject: Log Python location in CI --- .github/workflows/python-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index f5437b5e..b1f94946 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -39,6 +39,9 @@ jobs: - name: Log Python version run: >- python --version + - name: Log Python location + run: >- + which python - name: Cache pip uses: actions/cache@v1 with: -- cgit v1.2.3 From f4be7cfe1a897b443b998de932d1cd14e653a629 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 13:02:14 +0100 Subject: Fix running tests under Python 2 As per #1961. P.S. Using `contains(['2.7', 'pypy2'], matrix.python-version)` does not work even though https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#contains promises arrays to be supported. --- .github/workflows/python-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index b1f94946..7b1dcac6 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -76,11 +76,13 @@ jobs: - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' run: | + ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version || 'pypy2' == matrix.python-version) && 'py27' || '$TOXENV' }} python -m tox --parallel auto --notest --skip-missing-interpreters false env: ${{ matrix.env }} - name: Test with tox run: | ${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 + ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version || 'pypy2' == matrix.python-version) && 'py27' || '$TOXENV' }} python -m tox \ --parallel 0 \ -- \ -- cgit v1.2.3 From f8af062c7345e6aaf82eed9a5fdf9f04c68d275f Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 12:46:34 +0100 Subject: =?UTF-8?q?Add=20PyPy=20=F0=9F=90=8D=20jobs=20to=20Actions=20CI/CD?= =?UTF-8?q?=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/python-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 7b1dcac6..286e022c 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -15,6 +15,7 @@ jobs: matrix: python-version: - 3.8 + - pypy3 - 3.7 - 3.6 - 3.5 -- cgit v1.2.3 From 53d1343eacf565be5bf885960989c106accfb691 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 13:46:08 +0100 Subject: Support PyPy2 --- .github/workflows/python-tests.yml | 4 ++-- tox.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 286e022c..4dc997e5 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -77,13 +77,13 @@ jobs: - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' run: | - ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version || 'pypy2' == matrix.python-version) && 'py27' || '$TOXENV' }} + ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} python -m tox --parallel auto --notest --skip-missing-interpreters false env: ${{ matrix.env }} - name: Test with tox run: | ${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 - ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version || 'pypy2' == matrix.python-version) && 'py27' || '$TOXENV' }} + ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} python -m tox \ --parallel 0 \ -- \ diff --git a/tox.ini b/tox.ini index a70dff87..683b1454 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} - py27: PIP_IGNORE_REQUIRES_PYTHON=true + py{27,py2}: PIP_IGNORE_REQUIRES_PYTHON=true # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED -- cgit v1.2.3 From d83f1b864492279ed11ddd9715b15805fc6b39d0 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 14 Jan 2020 14:20:54 +0100 Subject: Set TOXENV to pypy3 on macOS @ GH Actions CI/CD --- .github/workflows/python-tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 4dc997e5..4c02a1db 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -78,12 +78,16 @@ jobs: - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' run: | ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} + ${{ startsWith(matrix.os, 'macOS-') && 'pypy3' == matrix.python-version && 'export TOXENV=pypy3' || '' }} + echo TOXENV="$TOXENV" python -m tox --parallel auto --notest --skip-missing-interpreters false env: ${{ matrix.env }} - name: Test with tox run: | ${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} + ${{ startsWith(matrix.os, 'macOS-') && 'pypy3' == matrix.python-version && 'export TOXENV=pypy3' || '' }} + echo TOXENV="$TOXENV" python -m tox \ --parallel 0 \ -- \ -- cgit v1.2.3 From 673e62b219cf6adbb2139bbec1c53f6d89d3c60f Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 20 Jan 2020 03:08:08 +0100 Subject: Simplify GH Actions tests workflow --- .github/workflows/python-tests.yml | 48 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 4c02a1db..3c2aca34 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -1,4 +1,6 @@ -name: Test suite +name: >- + 👷 + Test suite on: push: @@ -8,7 +10,10 @@ on: jobs: tests: - name: 👷 + name: >- + ${{ matrix.python-version }} + / + ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: # max-parallel: 5 @@ -25,11 +30,10 @@ jobs: - macOS-latest # - windows-2019 # - windows-2016 - env: - - TOXENV: python env: TOX_PARALLEL_NO_SPINNER: 1 + TOXENV: python steps: - uses: actions/checkout@master @@ -43,7 +47,10 @@ jobs: - name: Log Python location run: >- which python - - name: Cache pip + - name: Log Python env + run: >- + python -m sysconfig + - name: Pip cache uses: actions/cache@v1 with: path: ~/.cache/pip @@ -64,10 +71,13 @@ jobs: - name: Log installed dists run: >- 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 run: >- env - env: ${{ matrix.env }} - name: Verify that there's no cached Python modules in sources if: >- @@ -76,20 +86,16 @@ jobs: ! grep pyc setuptools.egg-info/SOURCES.txt - name: 'Initialize tox envs: ${{ matrix.env.TOXENV }}' - run: | - ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} - ${{ startsWith(matrix.os, 'macOS-') && 'pypy3' == matrix.python-version && 'export TOXENV=pypy3' || '' }} - echo TOXENV="$TOXENV" - python -m tox --parallel auto --notest --skip-missing-interpreters false - env: ${{ matrix.env }} + run: >- + python -m + tox + --parallel auto + --notest + --skip-missing-interpreters false - name: Test with tox - run: | - ${{ startsWith(matrix.os, 'windows-') && 'setx NETWORK_REQUIRED ' || 'export NETWORK_REQUIRED=' }}1 - ${{ startsWith(matrix.os, 'windows-') && 'setx TOXENV ' || 'export TOXENV=' }}${{ ('2.7' == matrix.python-version && 'py27') || ('pypy2' == matrix.python-version && 'pypy2') || '$TOXENV' }} - ${{ startsWith(matrix.os, 'macOS-') && 'pypy3' == matrix.python-version && 'export TOXENV=pypy3' || '' }} - echo TOXENV="$TOXENV" - python -m tox \ - --parallel 0 \ - -- \ + run: >- + python -m + tox + --parallel auto + -- --cov - env: ${{ matrix.env }} -- cgit v1.2.3 From 8414998c9c026659144065ab0a8cb2e2956e5a46 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 20 Jan 2020 03:10:27 +0100 Subject: Add back NETWORK_REQUIRED env var --- .github/workflows/python-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 3c2aca34..9d24a54a 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -32,6 +32,7 @@ jobs: # - windows-2016 env: + NETWORK_REQUIRED: 1 TOX_PARALLEL_NO_SPINNER: 1 TOXENV: python -- cgit v1.2.3 From 3a60d1de2d4b9cdf330158fd708dde3c23d6b344 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Jan 2020 09:31:59 -0500 Subject: Clarify subject of change. Ref #1905. --- changelog.d/1905.change.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/1905.change.rst b/changelog.d/1905.change.rst index 7b73898c..f0e07a18 100644 --- a/changelog.d/1905.change.rst +++ b/changelog.d/1905.change.rst @@ -1 +1 @@ -Fixed test failure introduced in 41.6.0 when the 'tests' directory is not present. +Fixed defect in _imp, introduced in 41.6.0 when the 'tests' directory is not present. -- cgit v1.2.3 From d92f778b6c88c8a36c4aecac9543d9297c425096 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Jan 2020 10:34:25 -0500 Subject: Rewrite selective PIP_IGNORE_REQUIRES_PYTHON to allow test dependencies in metadata to be installed without ignoring REQUIRES_PYTHON --- setuptools/tests/test_virtualenv.py | 11 +++++++++++ tools/tox_pip.py | 39 ++++++++++++++++++++++++++++++------- tox.ini | 1 - 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index b009fbd6..6549a6c0 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -12,6 +12,17 @@ from .textwrap import DALS 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) def pytest_virtualenv_works(virtualenv): """ diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 1b7eeda5..6f457bb2 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -38,18 +38,43 @@ def is_install_self(args): ) -def pip(args): - # Honor requires-python when installing test suite dependencies - if any('-r' in arg for arg in args): - os.environ['PIP_IGNORE_REQUIRES_PYTHON'] = '0' +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): if is_install_self(args): remove_setuptools() bootstrap() + sys.version_info > (3,) or disable_python_requires() - cmd = [sys.executable, '-m', 'pip'] + args - subprocess.check_call(cmd) + pip(*args) if __name__ == '__main__': - pip(sys.argv[1:]) + run(sys.argv[1:]) diff --git a/tox.ini b/tox.ini index aeae13ce..a5ce930f 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,6 @@ install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} - py27: PIP_IGNORE_REQUIRES_PYTHON=true # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED -- cgit v1.2.3 From f9e279df7d75f2bfcba92c3393c4c64e25e6dcb7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Jan 2020 11:31:23 -0500 Subject: Move test dependencies into the package, removing 'tests' directory which masks the error reported in #1896. --- setuptools/tests/requirements.txt | 12 ++++++++++++ tests/requirements.txt | 12 ------------ tox.ini | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 setuptools/tests/requirements.txt delete mode 100644 tests/requirements.txt diff --git a/setuptools/tests/requirements.txt b/setuptools/tests/requirements.txt new file mode 100644 index 00000000..cb3e6726 --- /dev/null +++ b/setuptools/tests/requirements.txt @@ -0,0 +1,12 @@ +mock +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.7 +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. diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index cb3e6726..00000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -mock -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.7 -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. diff --git a/tox.ini b/tox.ini index e0eef95a..048e6ea2 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ envlist=python [testenv] -deps=-rtests/requirements.txt +deps=-rsetuptools/tests/requirements.txt # Changed from default (`python -m pip ...`) # to prevent the current working directory # from being added to `sys.path`. -- cgit v1.2.3 From 461ea400887a576dba102816f72af1d6966e1768 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Jan 2020 22:09:46 -0500 Subject: Opt into PEP 517 for pip installs to avoid implicit dependency on Setuptools to install test dependencies. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index a5ce930f..3eb408cc 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} + PIP_USE_PEP517=1 # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED -- cgit v1.2.3 From 620bd62c00985b551dde326619a6c895d7c80e63 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Jan 2020 22:07:49 -0500 Subject: Set PIP_USE_PEP517 in installer command so as not to influence the tests with that setting. --- tools/tox_pip.py | 2 ++ tox.ini | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 6f457bb2..9fe4f905 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -68,6 +68,8 @@ def disable_python_requires(): def run(args): + os.environ['PIP_USE_PEP517'] = 'true' + if is_install_self(args): remove_setuptools() bootstrap() diff --git a/tox.ini b/tox.ini index 3eb408cc..a5ce930f 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,6 @@ install_command = {[helpers]pip} install {opts} {packages} list_dependencies_command = {[helpers]pip} freeze --all setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} - PIP_USE_PEP517=1 # TODO: The passed environment variables came from copying other tox.ini files # These should probably be individually annotated to explain what needs them. passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED -- cgit v1.2.3 From 641e088d193142f236d69c78d272cf7ece0d693c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 21 Jan 2020 22:38:03 -0500 Subject: Move docs dependencies into package metadata and use same technique as jaraco/skeleton to build docs in tox and rtd. --- .github/workflows/python-tests.yml | 2 +- .readthedocs.yml | 5 +++-- docs/requirements.txt | 5 ----- setup.cfg | 7 +++++++ tox.ini | 10 +++++----- 5 files changed, 16 insertions(+), 13 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 60765b52..e3663cf0 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -55,7 +55,7 @@ jobs: uses: actions/cache@v1 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements.txt') }}-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }} + key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} restore-keys: | ${{ runner.os }}-pip- ${{ runner.os }}- diff --git a/.readthedocs.yml b/.readthedocs.yml index 3aef6b6b..7b994a35 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,4 +1,5 @@ python: version: 3 - requirements_file: docs/requirements.txt - pip_install: false + extra_requirements: + - docs + pip_install: true diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index bc27165b..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -sphinx!=1.8.0 -rst.linker>=1.9 -jaraco.packaging>=6.1 - -setuptools>=34 diff --git a/setup.cfg b/setup.cfg index 87c25a62..bc34b22c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,8 +55,10 @@ exclude = *.tests [options.extras_require] ssl = wincertstore==0.2; sys_platform=='win32' + certs = certifi==2016.9.26 + tests = mock pytest-flake8 @@ -70,3 +72,8 @@ tests = paver; python_version>="3.6" futures; python_version=="2.7" pip>=19.1 # For proper file:// URLs support. + +docs = + sphinx + jaraco.packaging>=6.1 + rst.linker>=1.9 diff --git a/tox.ini b/tox.ini index a5ce930f..2164599f 100644 --- a/tox.ini +++ b/tox.ini @@ -44,12 +44,12 @@ skip_install=True commands=codecov -X gcov --file {toxworkdir}/coverage.xml [testenv:docs] -deps = -r{toxinidir}/docs/requirements.txt -skip_install=True +extras = + docs + testing +changedir = docs commands = - python -m bootstrap - sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/build/html - sphinx-build -W -b man -d {envtmpdir}/doctrees docs docs/build/man + python -m sphinx . {toxinidir}/build/html [coverage:run] source= -- cgit v1.2.3 From 60da370778026872e44d44c3a0429bfc2b242504 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 3 Feb 2020 15:05:14 +0100 Subject: Fix install_scripts() if bdist_wininst is missing Closes #1985 --- setuptools/command/install_scripts.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py index 16234273..8c9a15e2 100644 --- a/setuptools/command/install_scripts.py +++ b/setuptools/command/install_scripts.py @@ -32,8 +32,11 @@ class install_scripts(orig.install_scripts): ) bs_cmd = self.get_finalized_command('build_scripts') exec_param = getattr(bs_cmd, 'executable', None) - bw_cmd = self.get_finalized_command("bdist_wininst") - is_wininst = getattr(bw_cmd, '_is_running', False) + try: + bw_cmd = self.get_finalized_command("bdist_wininst") + is_wininst = getattr(bw_cmd, '_is_running', False) + except ImportError: + is_wininst = False writer = ei.ScriptWriter if is_wininst: exec_param = "python.exe" -- cgit v1.2.3 From 91ede46d247291a5e5449403880c15a2674ebcb5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Feb 2020 05:34:17 -0500 Subject: Add changelog entry. --- changelog.d/1981.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1981.change.rst diff --git a/changelog.d/1981.change.rst b/changelog.d/1981.change.rst new file mode 100644 index 00000000..c5713d9b --- /dev/null +++ b/changelog.d/1981.change.rst @@ -0,0 +1 @@ +Setuptools now declares its ``tests`` and ``docs`` dependencies in metadata (extras). -- cgit v1.2.3 From b0ecd44b1011db9c562019caec7e4ba2750ce4a6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 5 Feb 2020 18:50:05 -0500 Subject: Update changelog. --- changelog.d/1985.change.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1985.change.rst diff --git a/changelog.d/1985.change.rst b/changelog.d/1985.change.rst new file mode 100644 index 00000000..33698b88 --- /dev/null +++ b/changelog.d/1985.change.rst @@ -0,0 +1 @@ +Add support for installing scripts in environments where bdist_wininst is missing (i.e. Python 3.9). -- cgit v1.2.3 From a5dec2f14e3414e4ee5dd146bff9c289d573de9a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 8 Feb 2020 23:18:21 -0500 Subject: =?UTF-8?q?Bump=20version:=2045.1.0=20=E2=86=92=2045.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- CHANGES.rst | 13 +++++++++++++ changelog.d/1905.change.rst | 1 - changelog.d/1941.change.rst | 4 ---- changelog.d/1968.misc.rst | 1 - changelog.d/1981.change.rst | 1 - changelog.d/1985.change.rst | 1 - setup.cfg | 2 +- 8 files changed, 15 insertions(+), 10 deletions(-) delete mode 100644 changelog.d/1905.change.rst delete mode 100644 changelog.d/1941.change.rst delete mode 100644 changelog.d/1968.misc.rst delete mode 100644 changelog.d/1981.change.rst delete mode 100644 changelog.d/1985.change.rst diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ef8a3877..8e86f31f 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 45.1.0 +current_version = 45.2.0 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 198854fc..f97f5142 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,16 @@ +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 ------- diff --git a/changelog.d/1905.change.rst b/changelog.d/1905.change.rst deleted file mode 100644 index f0e07a18..00000000 --- a/changelog.d/1905.change.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed defect in _imp, introduced in 41.6.0 when the 'tests' directory is not present. diff --git a/changelog.d/1941.change.rst b/changelog.d/1941.change.rst deleted file mode 100644 index a41cdcfe..00000000 --- a/changelog.d/1941.change.rst +++ /dev/null @@ -1,4 +0,0 @@ -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. diff --git a/changelog.d/1968.misc.rst b/changelog.d/1968.misc.rst deleted file mode 100644 index 4aa5343f..00000000 --- a/changelog.d/1968.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Add flake8-2020 to check for misuse of sys.version or sys.version_info. diff --git a/changelog.d/1981.change.rst b/changelog.d/1981.change.rst deleted file mode 100644 index c5713d9b..00000000 --- a/changelog.d/1981.change.rst +++ /dev/null @@ -1 +0,0 @@ -Setuptools now declares its ``tests`` and ``docs`` dependencies in metadata (extras). diff --git a/changelog.d/1985.change.rst b/changelog.d/1985.change.rst deleted file mode 100644 index 33698b88..00000000 --- a/changelog.d/1985.change.rst +++ /dev/null @@ -1 +0,0 @@ -Add support for installing scripts in environments where bdist_wininst is missing (i.e. Python 3.9). diff --git a/setup.cfg b/setup.cfg index bc34b22c..8cca6477 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ formats = zip [metadata] name = setuptools -version = 45.1.0 +version = 45.2.0 description = Easily download, build, install, upgrade, and uninstall Python packages author = Python Packaging Authority author_email = distutils-sig@python.org -- cgit v1.2.3