summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2014-09-29 21:14:51 -0400
committerJason R. Coombs <jaraco@jaraco.com>2014-09-29 21:14:51 -0400
commit459fa2041143a2ee9a221b21191d5dfc5d444ebd (patch)
tree10a62f8bca69d5b035079d764e494f50701eee15
parent2d6594b74de045821a7dbb4dd88b46a51af312e4 (diff)
parentfad1cf2b4cec5fc9b48608ef4c34c2713842e2e0 (diff)
downloadpython-setuptools-bitbucket-459fa2041143a2ee9a221b21191d5dfc5d444ebd.tar.gz
Merge with 6.0.2
-rw-r--r--.travis.yml4
-rw-r--r--CHANGES.txt98
-rwxr-xr-xREADME.txt22
-rw-r--r--docs/conf.py11
-rw-r--r--docs/easy_install.txt8
-rw-r--r--docs/formats.txt4
-rw-r--r--docs/merge-faq.txt2
-rw-r--r--docs/pkg_resources.txt6
-rw-r--r--docs/python3.txt2
-rw-r--r--docs/releases.txt2
-rw-r--r--docs/setuptools.txt8
-rw-r--r--ez_setup.py4
-rw-r--r--pkg_resources.py75
-rwxr-xr-xsetup.cfg2
-rwxr-xr-xsetup.py1
-rw-r--r--setuptools.egg-info/entry_points.txt1
-rwxr-xr-xsetuptools/command/egg_info.py2
-rw-r--r--setuptools/command/install_lib.py83
-rwxr-xr-xsetuptools/command/sdist.py8
-rw-r--r--setuptools/dist.py2
-rw-r--r--setuptools/extension.py6
-rw-r--r--setuptools/msvc9_support.py64
-rwxr-xr-xsetuptools/sandbox.py7
-rw-r--r--setuptools/ssl_support.py9
-rw-r--r--setuptools/svn_utils.py2
-rw-r--r--setuptools/tests/test_integration.py3
-rw-r--r--setuptools/tests/test_msvc9compiler.py157
-rw-r--r--setuptools/tests/test_resources.py27
-rw-r--r--setuptools/tests/test_sandbox.py10
-rw-r--r--setuptools/tests/test_sdist.py28
-rw-r--r--setuptools/tests/win_script_wrapper.txt8
-rw-r--r--setuptools/utils.py11
-rw-r--r--setuptools/version.py2
33 files changed, 555 insertions, 124 deletions
diff --git a/.travis.yml b/.travis.yml
index 022824f9..bc387f46 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,8 @@ python:
- pypy
# command to run tests
script:
+ # testing fix for https://bitbucket.org/hpk42/pytest/issue/555
+ - pip install --pre -i https://devpi.net/hpk/dev/ --upgrade pytest
- python setup.py test
- python setup.py ptr
- - python ez_setup.py --version 3.5.1
+ - python ez_setup.py --version 5.4.1
diff --git a/CHANGES.txt b/CHANGES.txt
index ef456a7e..0f5e56f4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,6 +2,104 @@
CHANGES
=======
+-----
+6.0.2
+-----
+
+* Issue #262: Fixed regression in pip install due to egg-info directories
+ being omitted. Re-opens Issue #118.
+
+-----
+6.0.1
+-----
+
+* Issue #259: Fixed regression with namespace package handling on ``single
+ version, externally managed`` installs.
+
+---
+6.0
+---
+
+* Issue #100: When building a distribution, Setuptools will no longer match
+ default files using platform-dependent case sensitivity, but rather will
+ only match the files if their case matches exactly. As a result, on Windows
+ and other case-insensitive file systems, files with names such as
+ 'readme.txt' or 'README.TXT' will be omitted from the distribution and a
+ warning will be issued indicating that 'README.txt' was not found. Other
+ filenames affected are:
+
+ - README.rst
+ - README
+ - setup.cfg
+ - setup.py (or the script name)
+ - test/test*.py
+
+ Any users producing distributions with filenames that match those above
+ case-insensitively, but not case-sensitively, should rename those files in
+ their repository for better portability.
+* Pull Request #72: When using ``single_version_externally_managed``, the
+ exclusion list now includes Python 3.2 ``__pycache__`` entries.
+* Pull Request #76 and Pull Request #78: lines in top_level.txt are now
+ ordered deterministically.
+* Issue #118: The egg-info directory is now no longer included in the list
+ of outputs.
+* Issue #258: Setuptools now patches distutils msvc9compiler to
+ recognize the specially-packaged compiler package for easy extension module
+ support on Python 2.6, 2.7, and 3.2.
+
+---
+5.8
+---
+
+* Issue #237: ``pkg_resources`` now uses explicit detection of Python 2 vs.
+ Python 3, supporting environments where builtins have been patched to make
+ Python 3 look more like Python 2.
+
+---
+5.7
+---
+
+* Issue #240: Based on real-world performance measures against 5.4, zip
+ manifests are now cached in all circumstances. The
+ ``PKG_RESOURCES_CACHE_ZIP_MANIFESTS`` environment variable is no longer
+ relevant. The observed "memory increase" referenced in the 5.4 release
+ notes and detailed in Issue #154 was likely not an increase over the status
+ quo, but rather only an increase over not storing the zip info at all.
+
+---
+5.6
+---
+
+* Issue #242: Use absolute imports in svn_utils to avoid issues if the
+ installing package adds an xml module to the path.
+
+-----
+5.5.1
+-----
+
+* Issue #239: Fix typo in 5.5 such that fix did not take.
+
+---
+5.5
+---
+
+* Issue #239: Setuptools now includes the setup_requires directive on
+ Distribution objects and validates the syntax just like install_requires
+ and tests_require directives.
+
+-----
+5.4.2
+-----
+
+* Issue #236: Corrected regression in execfile implementation for Python 2.6.
+
+-----
+5.4.1
+-----
+
+* Python #7776: (ssl_support) Correct usage of host for validation when
+ tunneling for HTTPS.
+
---
5.4
---
diff --git a/README.txt b/README.txt
index ad6386e5..9a142bf7 100755
--- a/README.txt
+++ b/README.txt
@@ -23,13 +23,14 @@ latest known stable release.
.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
-Windows 8 (Powershell)
-======================
+Windows (Powershell 3 or later)
+===============================
For best results, uninstall previous versions FIRST (see `Uninstalling`_).
-Using Windows 8 or later, it's possible to install with one simple Powershell
-command. Start up Powershell and paste this command::
+Using Windows 8 (which includes PowerShell 3) or earlier versions of Windows
+with PowerShell 3 installed, it's possible to install with one simple
+Powershell command. Start up Powershell and paste this command::
> (Invoke-WebRequest https://bootstrap.pypa.io/ez_setup.py).Content | python -
@@ -45,8 +46,8 @@ Python 2.7 installed::
> (Invoke-WebRequest https://bootstrap.pypa.io/ez_setup.py).Content | py -3 -
The recommended way to install setuptools on Windows is to download
-`ez_setup.py`_ and run it. The script will download the appropriate .egg
-file and install it for you.
+`ez_setup.py`_ and run it. The script will download the appropriate
+distribution file and install it for you.
Once installation is complete, you will find an ``easy_install`` program in
your Python ``Scripts`` subdirectory. For simple invocation and best results,
@@ -55,11 +56,12 @@ present. If you did a user-local install, the ``Scripts`` subdirectory is
``$env:APPDATA\Python\Scripts``.
-Windows 7 (or graphical install)
-================================
+Windows (simplified)
+====================
-For Windows 7 and earlier, download `ez_setup.py`_ using your favorite web
-browser or other technique and "run" that file.
+For Windows without PowerShell 3 or for installation without a command-line,
+download `ez_setup.py`_ using your preferred web browser or other technique
+and "run" that file.
Unix (wget)
diff --git a/docs/conf.py b/docs/conf.py
index 9929aaf6..8be5c3dd 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,14 +12,17 @@
# autogenerated file.
#
# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import setup as setup_script
+# 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.
-#sys.path.append(os.path.abspath('.'))
+
+# Allow Sphinx to find the setup command that is imported below, as referenced above.
+import sys, os
+sys.path.append(os.path.abspath('..'))
+
+import setup as setup_script
# -- General configuration -----------------------------------------------------
diff --git a/docs/easy_install.txt b/docs/easy_install.txt
index 6739ba16..8dd176fd 100644
--- a/docs/easy_install.txt
+++ b/docs/easy_install.txt
@@ -39,7 +39,7 @@ You will need at least Python 2.6. 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 installling to Python's primary ``site-packages`` directory. If this is
+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.)
@@ -915,7 +915,7 @@ Command-Line Options
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 separting them with commas. The
+ 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
@@ -1014,7 +1014,7 @@ application, simply set the OS environment of that application to a specific val
Use "virtualenv"
~~~~~~~~~~~~~~~~
"virtualenv" is a 3rd-party python package that effectively "clones" a python installation, thereby
-creating an isolated location to intall packages. The evolution of "virtualenv" started before the existence
+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.
@@ -1171,7 +1171,7 @@ History
* Fixed not HTML-decoding URLs scraped from web pages
0.6c5
- * Fixed ``.dll`` files on Cygwin not having executable permisions when an egg
+ * Fixed ``.dll`` files on Cygwin not having executable permissions when an egg
is installed unzipped.
0.6c4
diff --git a/docs/formats.txt b/docs/formats.txt
index 36954bef..5c461ecb 100644
--- a/docs/formats.txt
+++ b/docs/formats.txt
@@ -349,7 +349,7 @@ of the project's "traditional" scripts (i.e., those specified using the
``scripts`` keyword to ``setup()``). This is so that they can be
reconstituted when an ``.egg`` file is installed.
-The scripts are placed here using the disutils' standard
+The scripts are placed here using the distutils' standard
``install_scripts`` command, so any ``#!`` lines reflect the Python
installation where the egg was built. But instead of copying the
scripts to the local script installation directory, EasyInstall writes
@@ -595,7 +595,7 @@ order to one another that is defined by their ``PYTHONPATH`` and
The net result of these changes is that ``sys.path`` order will be
as follows at runtime:
-1. The ``sys.argv[0]`` directory, or an emtpy string if no script
+1. The ``sys.argv[0]`` directory, or an empty string if no script
is being executed.
2. All eggs installed by EasyInstall in any ``.pth`` file in each
diff --git a/docs/merge-faq.txt b/docs/merge-faq.txt
index 52013098..ea45f30c 100644
--- a/docs/merge-faq.txt
+++ b/docs/merge-faq.txt
@@ -58,7 +58,7 @@ Who is invited to contribute? Who is excluded?
While we've worked privately to initiate this merge due to the potential sensitivity of the topic, no one is excluded from this effort. We invite all members of the community, especially those most familiar with Python packaging and its challenges to join us in the effort.
-We have lots of ideas for how we'd like to improve the codebase, release process, everything. Like distribute, the post-merge setuptools will have its source hosted on bitbucket. (So if you're currently a distribute contributor, about the only thing that's going to change is the URL of the repository you follow.) Also like distribute, it'll support Python 3, and hopefully we'll soon merge Vinay Sajip's patches to make it run on Python 3 without needing 2to3 to be run on the code first.
+We have lots of ideas for how we'd like to improve the codebase, release process, everything. Like distribute, the post-merge setuptools will have its source hosted on Bitbucket. (So if you're currently a distribute contributor, about the only thing that's going to change is the URL of the repository you follow.) Also like distribute, it'll support Python 3, and hopefully we'll soon merge Vinay Sajip's patches to make it run on Python 3 without needing 2to3 to be run on the code first.
While we've worked privately to initiate this merge due to the potential sensitivity of the topic, no one is excluded from this effort. We invite all members of the community, especially those most familiar with Python packaging and its challenges to join us in the effort.
diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt
index 18b68db7..f4a768e4 100644
--- a/docs/pkg_resources.txt
+++ b/docs/pkg_resources.txt
@@ -62,7 +62,7 @@ importable distribution
pluggable distribution
An importable distribution whose filename unambiguously identifies its
- release (i.e. project and version), and whose contents unamabiguously
+ release (i.e. project and version), and whose contents unambiguously
specify what releases of other projects will satisfy its runtime
requirements.
@@ -1434,7 +1434,7 @@ shown here. The `manager` argument to the methods below must be an object
that supports the full `ResourceManager API`_ documented above.
``get_resource_filename(manager, resource_name)``
- Return a true filesystem path for `resource_name`, co-ordinating the
+ Return a true filesystem path for `resource_name`, coordinating the
extraction with `manager`, if the resource must be unpacked to the
filesystem.
@@ -1586,7 +1586,7 @@ Parsing Utilities
character is ``#`` are considered comment lines.)
If `strs` is not an instance of ``basestring``, it is iterated over, and
- each item is passed recursively to ``yield_lines()``, so that an arbitarily
+ each item is passed recursively to ``yield_lines()``, so that an arbitrarily
nested sequence of strings, or sequences of sequences of strings can be
flattened out to the lines contained therein. So for example, passing
a file object or a list of strings to ``yield_lines`` will both work.
diff --git a/docs/python3.txt b/docs/python3.txt
index 1e019951..df173000 100644
--- a/docs/python3.txt
+++ b/docs/python3.txt
@@ -69,7 +69,7 @@ before you run the test command, as the files otherwise will seem updated,
and no conversion will happen.
In general, if code doesn't seem to be converted, deleting the build directory
-and trying again is a good saferguard against the build directory getting
+and trying again is a good safeguard against the build directory getting
"out of sync" with the source directory.
Distributing Python 3 modules
diff --git a/docs/releases.txt b/docs/releases.txt
index 41a814bc..66c0896f 100644
--- a/docs/releases.txt
+++ b/docs/releases.txt
@@ -10,7 +10,7 @@ module. The script does some checks (some interactive) and fully automates
the release process.
A Setuptools release manager must have maintainer access on PyPI to the
-project and administrative access to the BitBucket project.
+project and administrative access to the Bitbucket project.
To make a release, run the following from a Mercurial checkout at the
revision slated for release::
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index a793af53..c3844cf2 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -479,7 +479,7 @@ script called ``baz``, you might do something like this::
'bar = other_module:some_func',
],
'gui_scripts': [
- 'baz = my_package_gui.start_func',
+ 'baz = my_package_gui:start_func',
]
}
)
@@ -520,7 +520,7 @@ as the following::
}
)
-Any eggs built from the above setup script will include a short excecutable
+Any eggs built from the above setup script will include a short executable
prelude that imports and calls ``main_func()`` from ``my_package.some_module``.
The prelude can be run on Unix-like platforms (including Mac and Linux) by
invoking the egg with ``/bin/sh``, or by enabling execute permissions on the
@@ -2180,7 +2180,7 @@ for future builds (even those run implicitly by the ``install`` command)::
setup.py build --compiler=mingw32 saveopts
-The ``saveopts`` command saves all options for every commmand specified on the
+The ``saveopts`` command saves all options for every command specified on the
command line to the project's local ``setup.cfg`` file, unless you use one of
the `configuration file options`_ to change where the options are saved. For
example, this command does the same as above, but saves the compiler setting
@@ -2350,7 +2350,7 @@ Note, by the way, that the metadata in your ``setup()`` call determines what
will be listed in PyPI for your package. Try to fill out as much of it as
possible, as it will save you a lot of trouble manually adding and updating
your PyPI listings. Just put it in ``setup.py`` and use the ``register``
-comamnd to keep PyPI up to date.
+command to keep PyPI up to date.
The ``upload`` command has a few options worth noting:
diff --git a/ez_setup.py b/ez_setup.py
index b017944d..c5d85d37 100644
--- a/ez_setup.py
+++ b/ez_setup.py
@@ -36,7 +36,7 @@ try:
except ImportError:
USER_SITE = None
-DEFAULT_VERSION = "5.5"
+DEFAULT_VERSION = "6.0.2"
DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
def _python_cmd(*args):
@@ -268,7 +268,7 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
Download setuptools from a specified location and return its filename
`version` should be a valid setuptools version number that is available
- as an egg for download under the `download_base` URL (which should end
+ as an sdist for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
diff --git a/pkg_resources.py b/pkg_resources.py
index 11debf65..517298c9 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -16,6 +16,7 @@ method.
import sys
import os
+import io
import time
import re
import imp
@@ -35,28 +36,19 @@ import email.parser
import tempfile
from pkgutil import get_importer
-try:
- from urlparse import urlparse, urlunparse
-except ImportError:
+PY3 = sys.version_info > (3,)
+PY2 = not PY3
+
+if PY3:
from urllib.parse import urlparse, urlunparse
-try:
- frozenset
-except NameError:
- from sets import ImmutableSet as frozenset
-try:
- basestring
- next = lambda o: o.next()
- from cStringIO import StringIO as BytesIO
-except NameError:
- basestring = str
- from io import BytesIO
- def execfile(fn, globs=None, locs=None):
- if globs is None:
- globs = globals()
- if locs is None:
- locs = globs
- exec(compile(open(fn).read(), fn, 'exec'), globs, locs)
+if PY2:
+ from urlparse import urlparse, urlunparse
+
+if PY3:
+ string_types = str,
+else:
+ string_types = str, eval('unicode')
# capture these to bypass sandboxing
from os import utime
@@ -81,15 +73,6 @@ try:
except ImportError:
pass
-def _bypass_ensure_directory(name, mode=0o777):
- # Sandbox-bypassing version of ensure_directory()
- if not WRITE_SUPPORT:
- raise IOError('"os.mkdir" not supported on this platform.')
- dirname, filename = split(name)
- if dirname and filename and not isdir(dirname):
- _bypass_ensure_directory(dirname)
- mkdir(dirname, mode)
-
_state_vars = {}
@@ -343,7 +326,7 @@ run_main = run_script
def get_distribution(dist):
"""Return a current distribution object for a Requirement or string"""
- if isinstance(dist, basestring):
+ if isinstance(dist, string_types):
dist = Requirement.parse(dist)
if isinstance(dist, Requirement):
dist = get_provider(dist)
@@ -1387,7 +1370,7 @@ class NullProvider:
return self._fn(self.module_path, resource_name)
def get_resource_stream(self, manager, resource_name):
- return BytesIO(self.get_resource_string(manager, resource_name))
+ return io.BytesIO(self.get_resource_string(manager, resource_name))
def get_resource_string(self, manager, resource_name):
return self._get(self._fn(self.module_path, resource_name))
@@ -1435,7 +1418,9 @@ class NullProvider:
script_filename = self._fn(self.egg_info, script)
namespace['__file__'] = script_filename
if os.path.exists(script_filename):
- execfile(script_filename, namespace, namespace)
+ source = open(script_filename).read()
+ code = compile(source, script_filename, 'exec')
+ exec(code, namespace, namespace)
else:
from linecache import cache
cache[script_filename] = (
@@ -1605,11 +1590,7 @@ class ZipProvider(EggProvider):
"""Resource support for zips and eggs"""
eagers = None
- _zip_manifests = (
- MemoizedZipManifests()
- if os.environ.get('PKG_RESOURCES_CACHE_ZIP_MANIFESTS') else
- ZipManifests()
- )
+ _zip_manifests = MemoizedZipManifests()
def __init__(self, module):
EggProvider.__init__(self, module)
@@ -2067,8 +2048,8 @@ def _set_parent_ns(packageName):
def yield_lines(strs):
- """Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
- if isinstance(strs, basestring):
+ """Yield non-empty/non-comment lines of a string or sequence"""
+ if isinstance(strs, string_types):
for s in strs.splitlines():
s = s.strip()
# skip blank lines/comments
@@ -2660,8 +2641,7 @@ def issue_warning(*args,**kw):
def parse_requirements(strs):
"""Yield ``Requirement`` objects for each specification in `strs`
- `strs` must be an instance of ``basestring``, or a (possibly-nested)
- iterable thereof.
+ `strs` must be a string, or a (possibly-nested) iterable thereof.
"""
# create a steppable iterator, so we can handle \-continuations
lines = iter(yield_lines(strs))
@@ -2762,7 +2742,7 @@ class Requirement:
# only get if we need it
if self.index:
item = item.parsed_version
- elif isinstance(item, basestring):
+ elif isinstance(item, string_types):
item = parse_version(item)
last = None
# -1, 0, 1
@@ -2828,6 +2808,17 @@ def ensure_directory(path):
if not os.path.isdir(dirname):
os.makedirs(dirname)
+
+def _bypass_ensure_directory(path, mode=0o777):
+ """Sandbox-bypassing version of ensure_directory()"""
+ if not WRITE_SUPPORT:
+ raise IOError('"os.mkdir" not supported on this platform.')
+ dirname, filename = split(path)
+ if dirname and filename and not isdir(dirname):
+ _bypass_ensure_directory(dirname)
+ mkdir(dirname, mode)
+
+
def split_sections(s):
"""Split a string or iterable thereof into (section, content) pairs
diff --git a/setup.cfg b/setup.cfg
index 7f5fc796..74a172a4 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,5 +21,5 @@ formats = gztar zip
universal=1
[pytest]
-addopts=--ignore tests/manual_test.py --ignore tests/shlib_test
+addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/shlib_test
norecursedirs=dist build *.egg
diff --git a/setup.py b/setup.py
index b2f3ab37..ee2848f7 100755
--- a/setup.py
+++ b/setup.py
@@ -146,6 +146,7 @@ setup_params = dict(
"extras_require = setuptools.dist:check_extras",
"install_requires = setuptools.dist:check_requirements",
"tests_require = setuptools.dist:check_requirements",
+ "setup_requires = setuptools.dist:check_requirements",
"entry_points = setuptools.dist:check_entry_points",
"test_suite = setuptools.dist:check_test_suite",
"zip_safe = setuptools.dist:assert_bool",
diff --git a/setuptools.egg-info/entry_points.txt b/setuptools.egg-info/entry_points.txt
index de842da8..72a5ffe0 100644
--- a/setuptools.egg-info/entry_points.txt
+++ b/setuptools.egg-info/entry_points.txt
@@ -36,6 +36,7 @@ install_requires = setuptools.dist:check_requirements
namespace_packages = setuptools.dist:check_nsp
package_data = setuptools.dist:check_package_data
packages = setuptools.dist:check_packages
+setup_requires = setuptools.dist:check_requirements
test_loader = setuptools.dist:check_importable
test_runner = setuptools.dist:check_importable
test_suite = setuptools.dist:check_test_suite
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index debb52e4..14ff0763 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -390,7 +390,7 @@ def write_toplevel_names(cmd, basename, filename):
for k in cmd.distribution.iter_distribution_names()
]
)
- cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n')
+ cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n')
def overwrite_arg(cmd, basename, filename):
diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py
index d7e117f0..9b772227 100644
--- a/setuptools/command/install_lib.py
+++ b/setuptools/command/install_lib.py
@@ -1,6 +1,7 @@
-import distutils.command.install_lib as orig
import os
-
+import imp
+from itertools import product, starmap
+import distutils.command.install_lib as orig
class install_lib(orig.install_lib):
"""Don't add compiled flags to filenames of non-Python files"""
@@ -13,19 +14,71 @@ class install_lib(orig.install_lib):
self.byte_compile(outfiles)
def get_exclusions(self):
- exclude = {}
- nsp = self.distribution.namespace_packages
- svem = (nsp and self.get_finalized_command('install')
- .single_version_externally_managed)
- if svem:
- for pkg in nsp:
- parts = pkg.split('.')
- while parts:
- pkgdir = os.path.join(self.install_dir, *parts)
- for f in '__init__.py', '__init__.pyc', '__init__.pyo':
- exclude[os.path.join(pkgdir, f)] = 1
- parts.pop()
- return exclude
+ """
+ Return a collections.Sized collections.Container of paths to be
+ excluded for single_version_externally_managed installations.
+ """
+ all_packages = (
+ pkg
+ for ns_pkg in self._get_SVEM_NSPs()
+ for pkg in self._all_packages(ns_pkg)
+ )
+
+ excl_specs = product(all_packages, self._gen_exclusion_paths())
+ return set(starmap(self._exclude_pkg_path, excl_specs))
+
+ def _exclude_pkg_path(self, pkg, exclusion_path):
+ """
+ Given a package name and exclusion path within that package,
+ compute the full exclusion path.
+ """
+ parts = pkg.split('.') + [exclusion_path]
+ return os.path.join(self.install_dir, *parts)
+
+ @staticmethod
+ def _all_packages(pkg_name):
+ """
+ >>> list(install_lib._all_packages('foo.bar.baz'))
+ ['foo.bar.baz', 'foo.bar', 'foo']
+ """
+ while pkg_name:
+ yield pkg_name
+ pkg_name, sep, child = pkg_name.rpartition('.')
+
+ def _get_SVEM_NSPs(self):
+ """
+ Get namespace packages (list) but only for
+ single_version_externally_managed installations and empty otherwise.
+ """
+ # TODO: is it necessary to short-circuit here? i.e. what's the cost
+ # if get_finalized_command is called even when namespace_packages is
+ # False?
+ if not self.distribution.namespace_packages:
+ return []
+
+ install_cmd = self.get_finalized_command('install')
+ svem = install_cmd.single_version_externally_managed
+
+ return self.distribution.namespace_packages if svem else []
+
+ @staticmethod
+ def _gen_exclusion_paths():
+ """
+ Generate file paths to be excluded for namespace packages (bytecode
+ cache files).
+ """
+ # always exclude the package module itself
+ yield '__init__.py'
+
+ yield '__init__.pyc'
+ yield '__init__.pyo'
+
+ if not hasattr(imp, 'get_tag'):
+ return
+
+ base = os.path.join('__pycache__', '__init__.' + imp.get_tag())
+ yield base + '.pyc'
+ yield base + '.pyo'
def copy_tree(
self, infile, outfile,
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index c99ad9b1..a15582c3 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -9,6 +9,8 @@ import sys
import six
from setuptools import svn_utils
+from setuptools.utils import cs_path_exists
+
import pkg_resources
READMES = ('README', 'README.rst', 'README.txt')
@@ -147,7 +149,7 @@ class sdist(orig.sdist):
alts = fn
got_it = 0
for fn in alts:
- if os.path.exists(fn):
+ if cs_path_exists(fn):
got_it = 1
self.filelist.append(fn)
break
@@ -156,14 +158,14 @@ class sdist(orig.sdist):
self.warn("standard file not found: should have one of " +
', '.join(alts))
else:
- if os.path.exists(fn):
+ if cs_path_exists(fn):
self.filelist.append(fn)
else:
self.warn("standard file '%s' not found" % fn)
optional = ['test/test*.py', 'setup.cfg']
for pattern in optional:
- files = list(filter(os.path.isfile, glob(pattern)))
+ files = list(filter(cs_path_exists, glob(pattern)))
if files:
self.filelist.extend(files)
diff --git a/setuptools/dist.py b/setuptools/dist.py
index c6ac1946..26c35c4c 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -260,7 +260,7 @@ class Distribution(_Distribution):
self.dependency_links = attrs.pop('dependency_links', [])
assert_string_list(self,'dependency_links',self.dependency_links)
if attrs and 'setup_requires' in attrs:
- self.fetch_build_eggs(attrs.pop('setup_requires'))
+ self.fetch_build_eggs(attrs['setup_requires'])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
if not hasattr(self,ep.name):
setattr(self,ep.name,None)
diff --git a/setuptools/extension.py b/setuptools/extension.py
index ab5908da..8178ed33 100644
--- a/setuptools/extension.py
+++ b/setuptools/extension.py
@@ -2,12 +2,16 @@ import sys
import re
import functools
import distutils.core
+import distutils.errors
import distutils.extension
-from setuptools.dist import _get_unpatched
+from .dist import _get_unpatched
+from . import msvc9_support
_Extension = _get_unpatched(distutils.core.Extension)
+msvc9_support.patch_for_specialized_compiler()
+
def have_pyrex():
"""
Return True if Cython or Pyrex can be imported.
diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py
new file mode 100644
index 00000000..d0be70e2
--- /dev/null
+++ b/setuptools/msvc9_support.py
@@ -0,0 +1,64 @@
+import sys
+
+try:
+ import distutils.msvc9compiler
+except ImportError:
+ pass
+
+unpatched = dict()
+
+def patch_for_specialized_compiler():
+ """
+ Patch functions in distutils.msvc9compiler to use the standalone compiler
+ build for Python (Windows only). Fall back to original behavior when the
+ standalone compiler is not available.
+ """
+ if 'distutils' not in globals():
+ # The module isn't available to be patched
+ return
+
+ if unpatched:
+ # Already patched
+ return
+
+ unpatched.update(vars(distutils.msvc9compiler))
+
+ distutils.msvc9compiler.find_vcvarsall = find_vcvarsall
+ distutils.msvc9compiler.query_vcvarsall = query_vcvarsall
+
+def find_vcvarsall(version):
+ Reg = distutils.msvc9compiler.Reg
+ VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+ try:
+ # Per-user installs register the compiler path here
+ productdir = Reg.get_value(VC_BASE % ('', version), "installdir")
+ except KeyError:
+ try:
+ # All-user installs on a 64-bit system register here
+ productdir = Reg.get_value(VC_BASE % ('Wow6432Node\\', version), "installdir")
+ except KeyError:
+ productdir = None
+
+ if productdir:
+ import os
+ vcvarsall = os.path.join(productdir, "vcvarsall.bat")
+ if os.path.isfile(vcvarsall):
+ return vcvarsall
+
+ return unpatched['find_vcvarsall'](version)
+
+def query_vcvarsall(version, *args, **kwargs):
+ try:
+ return unpatched['query_vcvarsall'](version, *args, **kwargs)
+ except distutils.errors.DistutilsPlatformError:
+ exc = sys.exc_info()[1]
+ if exc and "vcvarsall.bat" in exc.args[0]:
+ message = 'Microsoft Visual C++ %0.1f is required (%s).' % (version, exc.args[0])
+ if int(version) == 9:
+ # This redirection link is maintained by Microsoft.
+ # Contact vspython@microsoft.com if it needs updating.
+ raise distutils.errors.DistutilsPlatformError(
+ message + ' Get it from http://aka.ms/vcpython27'
+ )
+ raise distutils.errors.DistutilsPlatformError(message)
+ raise
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index 185f2573..1d23ba98 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -30,7 +30,12 @@ def _execfile(filename, globals, locals=None):
"""
Python 3 implementation of execfile.
"""
- with open(filename, 'rb') as stream:
+ mode = 'rb'
+ # Python 2.6 compile requires LF for newlines, so use deprecated
+ # Universal newlines support.
+ if sys.version_info < (2, 7):
+ mode += 'U'
+ with open(filename, mode) as stream:
script = stream.read()
if locals is None:
locals = globals
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
index b574f4b9..c618ea7c 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -173,12 +173,19 @@ class VerifyingHTTPSConn(HTTPSConnection):
if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
self.sock = sock
self._tunnel()
+ # http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7
+ # change self.host to mean the proxy server host when tunneling is
+ # being used. Adapt, since we are interested in the destination
+ # host for the match_hostname() comparison.
+ actual_host = self._tunnel_host
+ else:
+ actual_host = self.host
self.sock = ssl.wrap_socket(
sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
)
try:
- match_hostname(self.sock.getpeercert(), self.host)
+ match_hostname(self.sock.getpeercert(), actual_host)
except CertificateError:
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
diff --git a/setuptools/svn_utils.py b/setuptools/svn_utils.py
index 65e4b815..41d176bb 100644
--- a/setuptools/svn_utils.py
+++ b/setuptools/svn_utils.py
@@ -1,3 +1,5 @@
+from __future__ import absolute_import
+
import os
import re
import sys
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 7144aa6c..8d6c1e55 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -25,6 +25,9 @@ def install_context(request, tmpdir, monkeypatch):
install_dir = tmpdir.mkdir('install_dir')
def fin():
+ # undo the monkeypatch, particularly needed under
+ # windows because of kept handle on cwd
+ monkeypatch.undo()
new_cwd.remove()
user_base.remove()
user_site.remove()
diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py
new file mode 100644
index 00000000..970f7679
--- /dev/null
+++ b/setuptools/tests/test_msvc9compiler.py
@@ -0,0 +1,157 @@
+"""msvc9compiler monkey patch test
+
+This test ensures that importing setuptools is sufficient to replace
+the standard find_vcvarsall function with our patched version that
+finds the Visual C++ for Python package.
+"""
+
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+import distutils.errors
+import contextlib
+
+# importing only setuptools should apply the patch
+__import__('setuptools')
+
+class MockReg:
+ """Mock for distutils.msvc9compiler.Reg. We patch it
+ with an instance of this class that mocks out the
+ functions that access the registry.
+ """
+
+ def __init__(self, hkey_local_machine={}, hkey_current_user={}):
+ self.hklm = hkey_local_machine
+ self.hkcu = hkey_current_user
+
+ def __enter__(self):
+ self.original_read_keys = distutils.msvc9compiler.Reg.read_keys
+ self.original_read_values = distutils.msvc9compiler.Reg.read_values
+
+ _winreg = getattr(distutils.msvc9compiler, '_winreg', None)
+ winreg = getattr(distutils.msvc9compiler, 'winreg', _winreg)
+
+ hives = {
+ winreg.HKEY_CURRENT_USER: self.hkcu,
+ winreg.HKEY_LOCAL_MACHINE: self.hklm,
+ }
+
+ def read_keys(cls, base, key):
+ """Return list of registry keys."""
+ hive = hives.get(base, {})
+ return [k.rpartition('\\')[2]
+ for k in hive if k.startswith(key.lower())]
+
+ def read_values(cls, base, key):
+ """Return dict of registry keys and values."""
+ hive = hives.get(base, {})
+ return dict((k.rpartition('\\')[2], hive[k])
+ for k in hive if k.startswith(key.lower()))
+
+ distutils.msvc9compiler.Reg.read_keys = classmethod(read_keys)
+ distutils.msvc9compiler.Reg.read_values = classmethod(read_values)
+
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ distutils.msvc9compiler.Reg.read_keys = self.original_read_keys
+ distutils.msvc9compiler.Reg.read_values = self.original_read_values
+
+@contextlib.contextmanager
+def patch_env(**replacements):
+ """
+ In a context, patch the environment with replacements. Pass None values
+ to clear the values.
+ """
+ saved = dict(
+ (key, os.environ['key'])
+ for key in replacements
+ if key in os.environ
+ )
+
+ # remove values that are null
+ remove = (key for (key, value) in replacements.items() if value is None)
+ for key in list(remove):
+ os.environ.pop(key, None)
+ replacements.pop(key)
+
+ os.environ.update(replacements)
+
+ try:
+ yield saved
+ finally:
+ for key in replacements:
+ os.environ.pop(key, None)
+ os.environ.update(saved)
+
+class TestMSVC9Compiler(unittest.TestCase):
+
+ def test_find_vcvarsall_patch(self):
+ if not hasattr(distutils, 'msvc9compiler'):
+ # skip
+ return
+
+ self.assertEqual(
+ "setuptools.msvc9_support",
+ distutils.msvc9compiler.find_vcvarsall.__module__,
+ "find_vcvarsall was not patched"
+ )
+
+ find_vcvarsall = distutils.msvc9compiler.find_vcvarsall
+ query_vcvarsall = distutils.msvc9compiler.query_vcvarsall
+
+ # No registry entries or environment variable means we should
+ # not find anything
+ with patch_env(VS90COMNTOOLS=None):
+ with MockReg():
+ self.assertIsNone(find_vcvarsall(9.0))
+
+ try:
+ query_vcvarsall(9.0)
+ self.fail('Expected DistutilsPlatformError from query_vcvarsall()')
+ except distutils.errors.DistutilsPlatformError:
+ exc_message = str(sys.exc_info()[1])
+ self.assertIn('aka.ms/vcpython27', exc_message)
+
+ key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir'
+ key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir'
+
+ # Make two mock files so we can tell whether HCKU entries are
+ # preferred to HKLM entries.
+ mock_installdir_1 = tempfile.mkdtemp()
+ mock_vcvarsall_bat_1 = os.path.join(mock_installdir_1, 'vcvarsall.bat')
+ open(mock_vcvarsall_bat_1, 'w').close()
+ mock_installdir_2 = tempfile.mkdtemp()
+ mock_vcvarsall_bat_2 = os.path.join(mock_installdir_2, 'vcvarsall.bat')
+ open(mock_vcvarsall_bat_2, 'w').close()
+ try:
+ # Ensure we get the current user's setting first
+ with MockReg(
+ hkey_current_user={key_32: mock_installdir_1},
+ hkey_local_machine={
+ key_32: mock_installdir_2,
+ key_64: mock_installdir_2,
+ }
+ ):
+ self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0))
+
+ # Ensure we get the local machine value if it's there
+ with MockReg(hkey_local_machine={key_32: mock_installdir_2}):
+ self.assertEqual(mock_vcvarsall_bat_2, find_vcvarsall(9.0))
+
+ # Ensure we prefer the 64-bit local machine key
+ # (*not* the Wow6432Node key)
+ with MockReg(
+ hkey_local_machine={
+ # This *should* only exist on 32-bit machines
+ key_32: mock_installdir_1,
+ # This *should* only exist on 64-bit machines
+ key_64: mock_installdir_2,
+ }
+ ):
+ self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0))
+ finally:
+ shutil.rmtree(mock_installdir_1)
+ shutil.rmtree(mock_installdir_2)
diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py
index 2db87efa..b688cfe0 100644
--- a/setuptools/tests/test_resources.py
+++ b/setuptools/tests/test_resources.py
@@ -15,14 +15,10 @@ from pkg_resources import (parse_requirements, VersionConflict, parse_version,
from setuptools.command.easy_install import (get_script_header, is_sh,
nt_quote_arg)
+from .py26compat import skipIf
import six
-try:
- frozenset
-except NameError:
- from sets import ImmutableSet as frozenset
-
def safe_repr(obj, short=False):
""" copied from Python2.7"""
try:
@@ -576,13 +572,8 @@ class NamespaceTests(TestCase):
pkg_resources._namespace_packages = self._ns_pkgs.copy()
sys.path = self._prev_sys_path[:]
- def _assertIn(self, member, container):
- """ assertIn and assertTrue does not exist in Python2.3"""
- if member not in container:
- standardMsg = '%s not found in %s' % (safe_repr(member),
- safe_repr(container))
- self.fail(self._formatMessage(msg, standardMsg))
-
+ msg = "Test fails when /tmp is a symlink. See #231"
+ @skipIf(os.path.islink(tempfile.gettempdir()), msg)
def test_two_levels_deep(self):
"""
Test nested namespace packages
@@ -606,15 +597,17 @@ class NamespaceTests(TestCase):
pkg2_init.write(ns_str)
pkg2_init.close()
import pkg1
- self._assertIn("pkg1", pkg_resources._namespace_packages.keys())
+ assert "pkg1" in pkg_resources._namespace_packages
try:
import pkg1.pkg2
except ImportError:
self.fail("Setuptools tried to import the parent namespace package")
# check the _namespace_packages dict
- self._assertIn("pkg1.pkg2", pkg_resources._namespace_packages.keys())
- self.assertEqual(pkg_resources._namespace_packages["pkg1"], ["pkg1.pkg2"])
+ assert "pkg1.pkg2" in pkg_resources._namespace_packages
+ assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"]
# check the __path__ attribute contains both paths
- self.assertEqual(pkg1.pkg2.__path__, [
+ expected = [
os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"),
- os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2")])
+ os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2"),
+ ]
+ assert pkg1.pkg2.__path__ == expected
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index 06b3d434..6a890ebc 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -19,7 +19,7 @@ def has_win32com():
if not sys.platform.startswith('win32'):
return False
try:
- mod = __import__('win32com')
+ __import__('win32com')
except ImportError:
return False
return True
@@ -33,8 +33,6 @@ class TestSandbox(unittest.TestCase):
shutil.rmtree(self.dir)
def test_devnull(self):
- if sys.version < '2.4':
- return
sandbox = DirectorySandbox(self.dir)
sandbox.run(self._file_writer(os.devnull))
@@ -75,5 +73,11 @@ class TestSandbox(unittest.TestCase):
setuptools.sandbox._execfile(target, vars(namespace))
assert namespace.result == 'passed'
+ def test_setup_py_with_CRLF(self):
+ setup_py = os.path.join(self.dir, 'setup.py')
+ with open(setup_py, 'wb') as stream:
+ stream.write(b'"degenerate script"\r\n')
+ setuptools.sandbox._execfile(setup_py, globals())
+
if __name__ == '__main__':
unittest.main()
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index c78e5b0f..26b072cc 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -87,6 +87,7 @@ class TestSdistTest(unittest.TestCase):
f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
f.write(SETUP_PY)
f.close()
+
# Set up the rest of the test package
test_pkg = os.path.join(self.temp_dir, 'sdist_test')
os.mkdir(test_pkg)
@@ -122,6 +123,33 @@ class TestSdistTest(unittest.TestCase):
self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest)
self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest)
+
+ def test_defaults_case_sensitivity(self):
+ """
+ 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()
+
+ dist = Distribution(SETUP_ATTRS)
+ # the extension deliberately capitalized for this test
+ # to make sure the actual filename (not capitalized) gets added
+ # to the manifest
+ dist.script_name = 'setup.PY'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ with quiet():
+ cmd.run()
+
+ # lowercase all names so we can test in a case-insensitive way to make sure the files are not included
+ manifest = map(lambda x: x.lower(), cmd.filelist.files)
+ self.assertFalse('readme.rst' in manifest, manifest)
+ self.assertFalse('setup.py' in manifest, manifest)
+ self.assertFalse('setup.cfg' in manifest, manifest)
+
def test_manifest_is_written_with_utf8_encoding(self):
# Test for #303.
dist = Distribution(SETUP_ATTRS)
diff --git a/setuptools/tests/win_script_wrapper.txt b/setuptools/tests/win_script_wrapper.txt
index 731243dd..b3a52e0a 100644
--- a/setuptools/tests/win_script_wrapper.txt
+++ b/setuptools/tests/win_script_wrapper.txt
@@ -3,7 +3,7 @@ Python Script Wrapper for Windows
setuptools includes wrappers for Python scripts that allows them to be
executed like regular windows programs. There are 2 wrappers, once
-for command-line programs, cli.exe, and one for graphica programs,
+for command-line programs, cli.exe, and one for graphical programs,
gui.exe. These programs are almost identical, function pretty much
the same way, and are generated from the same source file. The
wrapper programs are used by copying them to the directory containing
@@ -44,7 +44,7 @@ We'll also copy cli.exe to the sample-directory with the name foo.exe:
When the copy of cli.exe, foo.exe in this example, runs, it examines
the path name it was run with and computes a Python script path name
-by removing the '.exe' suffic and adding the '-script.py' suffix. (For
+by removing the '.exe' suffix and adding the '-script.py' suffix. (For
GUI programs, the suffix '-script-pyw' is added.) This is why we
named out script the way we did. Now we can run out script by running
the wrapper:
@@ -68,8 +68,8 @@ This example was a little pathological in that it exercised windows
- Double quotes in strings need to be escaped by preceding them with
back slashes.
-- One or more backslashes preceding double quotes quotes need to be
- escaped by preceding each of them them with back slashes.
+- One or more backslashes preceding double quotes need to be escaped
+ by preceding each of them with back slashes.
Specifying Python Command-line Options
diff --git a/setuptools/utils.py b/setuptools/utils.py
new file mode 100644
index 00000000..91e4b87f
--- /dev/null
+++ b/setuptools/utils.py
@@ -0,0 +1,11 @@
+import os
+import os.path
+
+
+def cs_path_exists(fspath):
+ if not os.path.exists(fspath):
+ return False
+ # make absolute so we always have a directory
+ abspath = os.path.abspath(fspath)
+ directory, filename = os.path.split(abspath)
+ return filename in os.listdir(directory) \ No newline at end of file
diff --git a/setuptools/version.py b/setuptools/version.py
index 773d9307..c974945d 100644
--- a/setuptools/version.py
+++ b/setuptools/version.py
@@ -1 +1 @@
-__version__ = '5.5'
+__version__ = '6.0.2'