summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2015-03-06 17:04:35 -0500
committerJason R. Coombs <jaraco@jaraco.com>2015-03-06 17:04:35 -0500
commitdbb11565d5880616866a608ce04f93b2b3efedd8 (patch)
tree1ed31bcaf743f05ad91e4ef9742b0ea5245b0c22
parenta979a4494369abbe40fcf65738fc78fc5ae88d7d (diff)
parent4334bd87fdb0e60ed6e019ad2eb3ee103032d316 (diff)
downloadpython-setuptools-bitbucket-dbb11565d5880616866a608ce04f93b2b3efedd8.tar.gz
Merge pull request #25 from dhellmann/fix-tox
Fix tox settings so they work
-rw-r--r--.travis.yml4
-rw-r--r--CHANGES.txt111
-rw-r--r--MANIFEST.in1
-rw-r--r--docs/conf.py49
-rw-r--r--docs/developer-guide.txt17
-rw-r--r--docs/development.txt1
-rw-r--r--docs/index.txt1
-rw-r--r--docs/using.txt13
-rw-r--r--ez_setup.py183
-rw-r--r--linkify.py56
-rw-r--r--pkg_resources/__init__.py6
-rwxr-xr-xpytest.ini3
-rwxr-xr-xsetup.cfg5
-rwxr-xr-xsetup.py30
-rw-r--r--setuptools/command/bdist_egg.py13
-rw-r--r--setuptools/command/build_py.py17
-rwxr-xr-xsetuptools/command/easy_install.py282
-rwxr-xr-xsetuptools/command/install_scripts.py17
-rw-r--r--setuptools/dist.py5
-rw-r--r--setuptools/msvc9_support.py8
-rwxr-xr-xsetuptools/sandbox.py88
-rw-r--r--setuptools/tests/contexts.py10
-rw-r--r--setuptools/tests/fixtures.py5
-rw-r--r--setuptools/tests/py26compat.py5
-rw-r--r--setuptools/tests/test_easy_install.py214
-rw-r--r--setuptools/tests/test_integration.py6
-rw-r--r--setuptools/tests/test_msvc9compiler.py20
-rw-r--r--setuptools/tests/test_sandbox.py48
-rw-r--r--setuptools/version.py2
29 files changed, 747 insertions, 473 deletions
diff --git a/.travis.yml b/.travis.yml
index 0e648b38..45cace4b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,5 +11,5 @@ script:
# update egg_info based on setup.py in checkout
- python bootstrap.py
- - python setup.py ptr --addopts='-rs'
- - python ez_setup.py --version 10.2.1
+ - python setup.py test --addopts='-rs'
+ - python ez_setup.py --version 12.2
diff --git a/CHANGES.txt b/CHANGES.txt
index afec8156..608dd77d 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -3,6 +3,115 @@ CHANGES
=======
----
+14.0
+----
+
+* Bootstrap script now accepts ``--to-dir`` to customize save directory or
+ allow for re-use of existing repository of setuptools versions. See
+ Pull Request #112 for an alternate implementation.
+* Issue #285: ``easy_install`` no longer will default to installing
+ packages to the "user site packages" directory if it is itself installed
+ there. Instead, the user must pass ``--user`` in all cases to install
+ packages to the user site packages.
+ This behavior now matches that of "pip install". To configure
+ an environment to always install to the user site packages, consider
+ using the "install-dir" and "scripts-dir" parameters to easy_install
+ through an appropriate distutils config file.
+
+------
+13.0.2
+------
+
+* Issue #359: Include pytest.ini in the sdist so invocation of py.test on the
+ sdist honors the pytest configuration.
+
+------
+13.0.1
+------
+
+Re-release of 13.0. Intermittent connectivity issues caused the release
+process to fail and PyPI uploads no longer accept files for 13.0.
+
+----
+13.0
+----
+
+* Issue #356: Back out Pull Request #119 as it requires Setuptools 10 or later
+ as the source during an upgrade.
+* Removed build_py class from setup.py. According to 892f439d216e, this
+ functionality was added to support upgrades from old Distribute versions,
+ 0.6.5 and 0.6.6.
+
+----
+12.4
+----
+
+* Pull Request #119: Restore writing of ``setup_requires`` to metadata
+ (previously added in 8.4 and removed in 9.0).
+
+----
+12.3
+----
+
+* Documentation is now linked using the rst.linker package.
+* Fix ``setuptools.command.easy_install.extract_wininst_cfg()``
+ with Python 2.6 and 2.7.
+* Issue #354. Added documentation on building setuptools
+ documentation.
+
+----
+12.2
+----
+
+* Issue #345: Unload all modules under pkg_resources during
+ ``ez_setup.use_setuptools()``.
+* Issue #336: Removed deprecation from ``ez_setup.use_setuptools``,
+ as it is clearly still used by buildout's bootstrap. ``ez_setup``
+ remains deprecated for use by individual packages.
+* Simplified implementation of ``ez_setup.use_setuptools``.
+
+----
+12.1
+----
+
+* Pull Request #118: Soften warning for non-normalized versions in
+ Distribution.
+
+------
+12.0.5
+------
+
+* Issue #339: Correct Attribute reference in ``cant_write_to_target``.
+* Issue #336: Deprecated ``ez_setup.use_setuptools``.
+
+------
+12.0.4
+------
+
+* Issue #335: Fix script header generation on Windows.
+
+------
+12.0.3
+------
+
+* Fixed incorrect class attribute in ``install_scripts``. Tests would be nice.
+
+------
+12.0.2
+------
+
+* Issue #331: Fixed ``install_scripts`` command on Windows systems corrupting
+ the header.
+
+------
+12.0.1
+------
+
+* Restore ``setuptools.command.easy_install.sys_executable`` for pbr
+ compatibility. For the future, tools should construct a CommandSpec
+ explicitly.
+
+----
12.0
----
@@ -10,6 +119,8 @@ CHANGES
``build.executable``, such that an executable of "/usr/bin/env my-python" may
be specified. This means that systems with a specified executable whose name
has spaces in the path must be updated to escape or quote that value.
+* Deprecated ``easy_install.ScriptWriter.get_writer``, replaced by ``.best()``
+ with slightly different semantics (no force_windows flag).
------
11.3.1
diff --git a/MANIFEST.in b/MANIFEST.in
index 428bbd1e..8a4523a9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -9,3 +9,4 @@ include *.txt
include MANIFEST.in
include launcher.c
include msvc-build-launcher.cmd
+include pytest.ini
diff --git a/docs/conf.py b/docs/conf.py
index 5ea2e05e..24830987 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -28,7 +28,7 @@ import setup as setup_script
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['linkify']
+extensions = ['rst.linker']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -198,3 +198,50 @@ latex_documents = [
# If false, no module index is generated.
#latex_use_modindex = True
+
+link_files = {
+ 'CHANGES.txt': dict(
+ using=dict(
+ BB='https://bitbucket.org',
+ GH='https://github.com',
+ ),
+ replace=[
+ dict(
+ pattern=r"(Issue )?#(?P<issue>\d+)",
+ url='{BB}/pypa/setuptools/issue/{issue}',
+ ),
+ dict(
+ pattern=r"Pull Request ?#(?P<pull_request>\d+)",
+ url='{BB}/pypa/setuptools/pull-request/{pull_request}',
+ ),
+ dict(
+ pattern=r"Distribute #(?P<distribute>\d+)",
+ url='{BB}/tarek/distribute/issue/{distribute}',
+ ),
+ dict(
+ pattern=r"Buildout #(?P<buildout>\d+)",
+ url='{GH}/buildout/buildout/issues/{buildout}',
+ ),
+ dict(
+ pattern=r"Old Setuptools #(?P<old_setuptools>\d+)",
+ url='http://bugs.python.org/setuptools/issue{old_setuptools}',
+ ),
+ dict(
+ pattern=r"Jython #(?P<jython>\d+)",
+ url='http://bugs.jython.org/issue{jython}',
+ ),
+ dict(
+ pattern=r"Python #(?P<python>\d+)",
+ url='http://bugs.python.org/issue{python}',
+ ),
+ dict(
+ pattern=r"Interop #(?P<interop>\d+)",
+ url='{GH}/pypa/interoperability-peps/issues/{interop}',
+ ),
+ dict(
+ pattern=r"Pip #(?P<pip>\d+)",
+ url='{GH}/pypa/pip/issues/{pip}',
+ ),
+ ],
+ ),
+}
diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt
index 558d6ee7..27c304e5 100644
--- a/docs/developer-guide.txt
+++ b/docs/developer-guide.txt
@@ -109,3 +109,20 @@ Setuptools follows ``semver`` with some exceptions:
- Omits 'v' prefix for tags.
.. explain value of reflecting meaning in versions.
+
+----------------------
+Building Documentation
+----------------------
+
+Setuptools relies on the Sphinx system for building documentation and in
+particular the ``build_sphinx`` distutils command. To build the
+documentation, invoke::
+
+ python setup.py build_sphinx
+
+from the root of the repository. Setuptools will download a compatible
+build of Sphinx and any requisite plugins and then build the
+documentation in the build/sphinx directory.
+
+Setuptools does not support invoking the doc builder from the docs/
+directory as some tools expect.
diff --git a/docs/development.txt b/docs/development.txt
index 6fe30f6e..455f038a 100644
--- a/docs/development.txt
+++ b/docs/development.txt
@@ -33,4 +33,3 @@ setuptools changes. You have been warned.
developer-guide
formats
releases
-
diff --git a/docs/index.txt b/docs/index.txt
index d8eb968b..529f08f3 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -19,7 +19,6 @@ Documentation content:
history
roadmap
python3
- using
setuptools
easy_install
pkg_resources
diff --git a/docs/using.txt b/docs/using.txt
deleted file mode 100644
index bd80893d..00000000
--- a/docs/using.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-================================
-Using Setuptools in your project
-================================
-
-To use Setuptools in your project, the recommended way is to ship
-`ez_setup.py` alongside your `setup.py` script and call
-it at the very beginning of `setup.py` like this::
-
- from ez_setup import use_setuptools
- use_setuptools()
-
-More info on `ez_setup.py` can be found at `the project home page
-<https://pypy.python.org/pypi/setuptools>`_.
diff --git a/ez_setup.py b/ez_setup.py
index f1e4aeae..9ece89d7 100644
--- a/ez_setup.py
+++ b/ez_setup.py
@@ -1,18 +1,11 @@
#!/usr/bin/env python
-"""Bootstrap setuptools installation
-To use setuptools in your package's setup.py, include this
-file in the same directory and add this to the top of your setup.py::
-
- from ez_setup import use_setuptools
- use_setuptools()
-
-To require a specific version of setuptools, set a download
-mirror, or use an alternate download directory, simply supply
-the appropriate options to ``use_setuptools()``.
+"""
+Setuptools bootstrapping installer.
-This file can also be run as a script to install or upgrade setuptools.
+Run this script to install or upgrade setuptools.
"""
+
import os
import shutil
import sys
@@ -23,6 +16,7 @@ import subprocess
import platform
import textwrap
import contextlib
+import warnings
from distutils import log
@@ -36,11 +30,15 @@ try:
except ImportError:
USER_SITE = None
-DEFAULT_VERSION = "11.3.2"
+DEFAULT_VERSION = "13.0.2"
DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
+DEFAULT_SAVE_DIR = os.curdir
+
def _python_cmd(*args):
"""
+ Execute a command.
+
Return True if the command succeeded.
"""
args = (sys.executable,) + args
@@ -48,6 +46,7 @@ def _python_cmd(*args):
def _install(archive_filename, install_args=()):
+ """Install Setuptools."""
with archive_context(archive_filename):
# installing
log.warn('Installing Setuptools')
@@ -59,6 +58,7 @@ def _install(archive_filename, install_args=()):
def _build_egg(egg, archive_filename, to_dir):
+ """Build Setuptools egg."""
with archive_context(archive_filename):
# building an egg
log.warn('Building a Setuptools egg in %s', to_dir)
@@ -70,9 +70,8 @@ def _build_egg(egg, archive_filename, to_dir):
class ContextualZipFile(zipfile.ZipFile):
- """
- Supplement ZipFile class to support context manager for Python 2.6
- """
+
+ """Supplement ZipFile class to support context manager for Python 2.6."""
def __enter__(self):
return self
@@ -81,9 +80,7 @@ class ContextualZipFile(zipfile.ZipFile):
self.close()
def __new__(cls, *args, **kwargs):
- """
- Construct a ZipFile or ContextualZipFile as appropriate
- """
+ """Construct a ZipFile or ContextualZipFile as appropriate."""
if hasattr(zipfile.ZipFile, '__exit__'):
return zipfile.ZipFile(*args, **kwargs)
return super(ContextualZipFile, cls).__new__(cls)
@@ -91,7 +88,11 @@ class ContextualZipFile(zipfile.ZipFile):
@contextlib.contextmanager
def archive_context(filename):
- # extracting the archive
+ """
+ Unzip filename to a temporary directory, set to the cwd.
+
+ The unzipped target is cleaned up after.
+ """
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
@@ -112,6 +113,7 @@ def archive_context(filename):
def _do_download(version, download_base, to_dir, download_delay):
+ """Download Setuptools."""
egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
% (version, sys.version_info[0], sys.version_info[1]))
if not os.path.exists(egg):
@@ -129,41 +131,77 @@ def _do_download(version, download_base, to_dir, download_delay):
setuptools.bootstrap_install_from = egg
-def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
- to_dir=os.curdir, download_delay=15):
+def use_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=DEFAULT_SAVE_DIR, download_delay=15):
+ """
+ Ensure that a setuptools version is installed.
+
+ Return None. Raise SystemExit if the requested version
+ or later cannot be installed.
+ """
to_dir = os.path.abspath(to_dir)
+
+ # prior to importing, capture the module state for
+ # representative modules.
rep_modules = 'pkg_resources', 'setuptools'
imported = set(sys.modules).intersection(rep_modules)
+
try:
import pkg_resources
- except ImportError:
- return _do_download(version, download_base, to_dir, download_delay)
- try:
pkg_resources.require("setuptools>=" + version)
+ # a suitable version is already installed
return
+ except ImportError:
+ # pkg_resources not available; setuptools is not installed; download
+ pass
except pkg_resources.DistributionNotFound:
- return _do_download(version, download_base, to_dir, download_delay)
+ # no version of setuptools was found; allow download
+ pass
except pkg_resources.VersionConflict as VC_err:
if imported:
- msg = textwrap.dedent("""
- The required version of setuptools (>={version}) is not available,
- and can't be installed while this script is running. Please
- install a more recent version first, using
- 'easy_install -U setuptools'.
+ _conflict_bail(VC_err, version)
+
+ # otherwise, unload pkg_resources to allow the downloaded version to
+ # take precedence.
+ del pkg_resources
+ _unload_pkg_resources()
- (Currently using {VC_err.args[0]!r})
- """).format(VC_err=VC_err, version=version)
- sys.stderr.write(msg)
- sys.exit(2)
+ return _do_download(version, download_base, to_dir, download_delay)
+
+
+def _conflict_bail(VC_err, version):
+ """
+ Setuptools was imported prior to invocation, so it is
+ unsafe to unload it. Bail out.
+ """
+ conflict_tmpl = textwrap.dedent("""
+ The required version of setuptools (>={version}) is not available,
+ and can't be installed while this script is running. Please
+ install a more recent version first, using
+ 'easy_install -U setuptools'.
+
+ (Currently using {VC_err.args[0]!r})
+ """)
+ msg = conflict_tmpl.format(**locals())
+ sys.stderr.write(msg)
+ sys.exit(2)
+
+
+def _unload_pkg_resources():
+ del_modules = [
+ name for name in sys.modules
+ if name.startswith('pkg_resources')
+ ]
+ for mod_name in del_modules:
+ del sys.modules[mod_name]
- # otherwise, reload ok
- del pkg_resources, sys.modules['pkg_resources']
- return _do_download(version, download_base, to_dir, download_delay)
def _clean_check(cmd, target):
"""
- Run the command to download target. If the command fails, clean up before
- re-raising the error.
+ Run the command to download target.
+
+ If the command fails, clean up before re-raising the error.
"""
try:
subprocess.check_call(cmd)
@@ -172,10 +210,13 @@ def _clean_check(cmd, target):
os.unlink(target)
raise
+
def download_file_powershell(url, target):
"""
- Download the file at url to target using Powershell (which will validate
- trust). Raise an exception if the command cannot complete.
+ Download the file at url to target using Powershell.
+
+ Powershell will validate trust.
+ Raise an exception if the command cannot complete.
"""
target = os.path.abspath(target)
ps_cmd = (
@@ -191,7 +232,9 @@ def download_file_powershell(url, target):
]
_clean_check(cmd, target)
+
def has_powershell():
+ """Determine if Powershell is available."""
if platform.system() != 'Windows':
return False
cmd = ['powershell', '-Command', 'echo test']
@@ -201,13 +244,14 @@ def has_powershell():
except Exception:
return False
return True
-
download_file_powershell.viable = has_powershell
+
def download_file_curl(url, target):
cmd = ['curl', url, '--silent', '--output', target]
_clean_check(cmd, target)
+
def has_curl():
cmd = ['curl', '--version']
with open(os.path.devnull, 'wb') as devnull:
@@ -216,13 +260,14 @@ def has_curl():
except Exception:
return False
return True
-
download_file_curl.viable = has_curl
+
def download_file_wget(url, target):
cmd = ['wget', url, '--quiet', '--output-document', target]
_clean_check(cmd, target)
+
def has_wget():
cmd = ['wget', '--version']
with open(os.path.devnull, 'wb') as devnull:
@@ -231,14 +276,11 @@ def has_wget():
except Exception:
return False
return True
-
download_file_wget.viable = has_wget
+
def download_file_insecure(url, target):
- """
- Use Python to download the file, even though it cannot authenticate the
- connection.
- """
+ """Use Python to download the file, without connection authentication."""
src = urlopen(url)
try:
# Read all the data in one block.
@@ -249,9 +291,9 @@ def download_file_insecure(url, target):
# Write all the data in one block to avoid creating a partial file.
with open(target, "wb") as dst:
dst.write(data)
-
download_file_insecure.viable = lambda: True
+
def get_best_downloader():
downloaders = (
download_file_powershell,
@@ -262,10 +304,13 @@ def get_best_downloader():
viable_downloaders = (dl for dl in downloaders if dl.viable())
return next(viable_downloaders, None)
-def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
- to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader):
+
+def download_setuptools(
+ version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+ to_dir=DEFAULT_SAVE_DIR, delay=15,
+ downloader_factory=get_best_downloader):
"""
- Download setuptools from a specified location and return its filename
+ Download setuptools from a specified location and return its filename.
`version` should be a valid setuptools version number that is available
as an sdist for download under the `download_base` URL (which should end
@@ -287,16 +332,18 @@ def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
downloader(url, saveto)
return os.path.realpath(saveto)
+
def _build_install_args(options):
"""
- Build the arguments to 'python setup.py install' on the setuptools package
+ Build the arguments to 'python setup.py install' on the setuptools package.
+
+ Returns list of command line arguments.
"""
return ['--user'] if options.user_install else []
+
def _parse_args():
- """
- Parse the command line for options
- """
+ """Parse the command line for options."""
parser = optparse.OptionParser()
parser.add_option(
'--user', dest='user_install', action='store_true', default=False,
@@ -314,18 +361,30 @@ def _parse_args():
'--version', help="Specify which version to download",
default=DEFAULT_VERSION,
)
+ parser.add_option(
+ '--to-dir',
+ help="Directory to save (and re-use) package",
+ default=DEFAULT_SAVE_DIR,
+ )
options, args = parser.parse_args()
# positional arguments are ignored
return options
+
+def _download_args(options):
+ """Return args for download_setuptools function from cmdline args."""
+ return dict(
+ version=options.version,
+ download_base=options.download_base,
+ downloader_factory=options.downloader_factory,
+ to_dir=options.to_dir,
+ )
+
+
def main():
- """Install or upgrade setuptools and EasyInstall"""
+ """Install or upgrade setuptools and EasyInstall."""
options = _parse_args()
- archive = download_setuptools(
- version=options.version,
- download_base=options.download_base,
- downloader_factory=options.downloader_factory,
- )
+ archive = download_setuptools(**_download_args(options))
return _install(archive, _build_install_args(options))
if __name__ == '__main__':
diff --git a/linkify.py b/linkify.py
deleted file mode 100644
index 5c6e16b4..00000000
--- a/linkify.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-Sphinx plugin to add links to the changelog.
-"""
-
-import re
-import os
-
-
-link_patterns = [
- r"(Issue )?#(?P<issue>\d+)",
- r"Pull Request ?#(?P<pull_request>\d+)",
- r"Distribute #(?P<distribute>\d+)",
- r"Buildout #(?P<buildout>\d+)",
- r"Old Setuptools #(?P<old_setuptools>\d+)",
- r"Jython #(?P<jython>\d+)",
- r"Python #(?P<python>\d+)",
- r"Interop #(?P<interop>\d+)",
- r"Pip #(?P<pip>\d+)",
-]
-
-issue_urls = dict(
- pull_request='https://bitbucket.org'
- '/pypa/setuptools/pull-request/{pull_request}',
- issue='https://bitbucket.org/pypa/setuptools/issue/{issue}',
- distribute='https://bitbucket.org/tarek/distribute/issue/{distribute}',
- buildout='https://github.com/buildout/buildout/issues/{buildout}',
- old_setuptools='http://bugs.python.org/setuptools/issue{old_setuptools}',
- jython='http://bugs.jython.org/issue{jython}',
- python='http://bugs.python.org/issue{python}',
- interop='https://github.com/pypa/interoperability-peps/issues/{interop}',
- pip='https://github.com/pypa/pip/issues/{pip}',
-)
-
-
-def _linkify(source, dest):
- pattern = '|'.join(link_patterns)
- with open(source) as source:
- out = re.sub(pattern, replacer, source.read())
- with open(dest, 'w') as dest:
- dest.write(out)
-
-
-def replacer(match):
- text = match.group(0)
- match_dict = match.groupdict()
- for key in match_dict:
- if match_dict[key]:
- url = issue_urls[key].format(**match_dict)
- return "`{text} <{url}>`_".format(text=text, url=url)
-
-def setup(app):
- _linkify('CHANGES.txt', 'CHANGES (links).txt')
- app.connect('build-finished', remove_file)
-
-def remove_file(app, exception):
- os.remove('CHANGES (links).txt')
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index c0c095b2..2ce663d2 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -182,8 +182,10 @@ class _SetuptoolsVersionMixin(object):
"You have iterated over the result of "
"pkg_resources.parse_version. This is a legacy behavior which is "
"inconsistent with the new version class introduced in setuptools "
- "8.0. That class should be used directly instead of attempting to "
- "iterate over the result.",
+ "8.0. In most cases, conversion to a tuple is unnecessary. For "
+ "comparison of versions, sort the Version instances directly. If "
+ "you have another use case requiring the tuple, please file a "
+ "bug with the setuptools project describing that need.",
RuntimeWarning,
stacklevel=1,
)
diff --git a/pytest.ini b/pytest.ini
new file mode 100755
index 00000000..91d64bb8
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/shlib_test --doctest-glob=pkg_resources/api_tests.txt
+norecursedirs=dist build *.egg
diff --git a/setup.cfg b/setup.cfg
index 5937edd0..a6da2c77 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,6 +5,7 @@ tag_build = dev
release = egg_info -RDb ''
source = register sdist binary
binary = bdist_egg upload --show-response
+test = pytest
[build_sphinx]
source-dir = docs/
@@ -19,7 +20,3 @@ formats = gztar zip
[wheel]
universal=1
-
-[pytest]
-addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/shlib_test --doctest-glob=pkg_resources/api_tests.txt
-norecursedirs=dist build *.egg
diff --git a/setup.py b/setup.py
index b4b93b38..234a7885 100755
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,8 @@
#!/usr/bin/env python
-"""Distutils setup file, used to install or test 'setuptools'"""
+"""
+Distutils setup file, used to install or test 'setuptools'
+"""
+
import io
import os
import sys
@@ -25,7 +28,6 @@ with open(ver_path) as ver_file:
exec(ver_file.read(), main_ns)
import setuptools
-from setuptools.command.build_py import build_py as _build_py
scripts = []
@@ -46,20 +48,6 @@ def _gen_console_scripts():
console_scripts = list(_gen_console_scripts())
-
-# specific command that is used to generate windows .exe files
-class build_py(_build_py):
- def build_package_data(self):
- """Copy data files into build directory"""
- for package, src_dir, build_dir, filenames in self.data_files:
- for filename in filenames:
- target = os.path.join(build_dir, filename)
- self.mkpath(os.path.dirname(target))
- srcfile = os.path.join(src_dir, filename)
- outf, copied = self.copy_file(srcfile, target)
- srcfile = os.path.abspath(srcfile)
-
-
readme_file = io.open('README.txt', encoding='utf-8')
with readme_file:
@@ -75,7 +63,10 @@ if sys.platform == 'win32' or force_windows_specific_files:
package_data.setdefault('setuptools', []).extend(['*.exe'])
package_data.setdefault('setuptools.command', []).extend(['*.xml'])
-pytest_runner = ['pytest-runner'] if 'ptr' in sys.argv else []
+needs_pytest = set(['ptr', 'pytest', 'test']).intersection(sys.argv)
+pytest_runner = ['pytest-runner'] if needs_pytest else []
+needs_sphinx = set(['build_sphinx', 'upload_docs']).intersection(sys.argv)
+sphinx = ['sphinx', 'rst.linker'] if needs_sphinx else []
setup_params = dict(
name="setuptools",
@@ -170,10 +161,9 @@ setup_params = dict(
tests_require=[
'setuptools[ssl]',
'pytest',
- 'mock',
- ],
+ ] + (['mock'] if sys.version_info[:2] < (3, 3) else []),
setup_requires=[
- ] + pytest_runner,
+ ] + sphinx + pytest_runner,
)
if __name__ == '__main__':
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 34fdeec2..87dce882 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -2,7 +2,6 @@
Build .egg distributions"""
-# This module should be kept compatible with Python 2.3
from distutils.errors import DistutilsSetupError
from distutils.dir_util import remove_tree, mkpath
from distutils import log
@@ -406,10 +405,6 @@ def scan_module(egg_dir, base, name, stubs):
if bad in symbols:
log.warn("%s: module MAY be using inspect.%s", module, bad)
safe = False
- if '__name__' in symbols and '__main__' in symbols and '.' not in module:
- if sys.version[:3] == "2.4": # -m works w/zipfiles in 2.5
- log.warn("%s: top-level module may be 'python -m' script", module)
- safe = False
return safe
@@ -441,7 +436,7 @@ INSTALL_DIRECTORY_ATTRS = [
]
-def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
+def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True,
mode='w'):
"""Create a zip file from all the files under 'base_dir'. The output
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
@@ -463,11 +458,7 @@ def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
z.write(path, p)
log.debug("adding '%s'" % p)
- if compress is None:
- # avoid 2.3 zipimport bug when 64 bits
- compress = (sys.version >= "2.4")
-
- compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)]
+ compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
if not dry_run:
z = zipfile.ZipFile(zip_filename, mode, compression=compression)
for dirname, dirs, files in os.walk(base_dir):
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 98080694..a873d54b 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -136,22 +136,7 @@ class build_py(orig.build_py, Mixin2to3):
mf.setdefault(src_dirs[d], []).append(path)
def get_data_files(self):
- pass # kludge 2.4 for lazy computation
-
- if sys.version < "2.4": # Python 2.4 already has this code
- def get_outputs(self, include_bytecode=1):
- """Return complete list of files copied to the build directory
-
- This includes both '.py' files and data files, as well as '.pyc'
- and '.pyo' files if 'include_bytecode' is true. (This method is
- needed for the 'install_lib' command to do its job properly, and to
- generate a correct installation manifest.)
- """
- return orig.build_py.get_outputs(self, include_bytecode) + [
- os.path.join(build_dir, filename)
- for package, src_dir, build_dir, filenames in self.data_files
- for filename in filenames
- ]
+ pass # Lazily compute data files in _get_data_files() function.
def check_package(self, package, package_dir):
"""Check namespace packages' __init__ for declare_namespace"""
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 340b1fac..4e841520 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -37,6 +37,7 @@ import struct
import contextlib
import subprocess
import shlex
+import io
from setuptools import Command
from setuptools.sandbox import run_setup
@@ -56,7 +57,6 @@ from pkg_resources import (
)
import pkg_resources
-
# Turn on PEP440Warnings
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
@@ -152,12 +152,9 @@ class easy_install(Command):
create_index = PackageIndex
def initialize_options(self):
- if site.ENABLE_USER_SITE:
- whereami = os.path.abspath(__file__)
- self.user = whereami.startswith(site.USER_SITE)
- else:
- self.user = 0
-
+ # the --user option seemst to be an opt-in one,
+ # so the default should be False.
+ self.user = 0
self.zip_ok = self.local_snapshots_ok = None
self.install_dir = self.script_dir = self.exclude_scripts = None
self.index_url = None
@@ -446,43 +443,49 @@ class easy_install(Command):
self.pth_file = None # and don't create a .pth file
self.install_dir = instdir
- def cant_write_to_target(self):
- template = """can't create or remove files in install directory
+ __cant_write_msg = textwrap.dedent("""
+ can't create or remove files in install directory
-The following error occurred while trying to add or remove files in the
-installation directory:
+ The following error occurred while trying to add or remove files in the
+ installation directory:
- %s
+ %s
-The installation directory you specified (via --install-dir, --prefix, or
-the distutils default setting) was:
+ The installation directory you specified (via --install-dir, --prefix, or
+ the distutils default setting) was:
- %s
-"""
- msg = template % (sys.exc_info()[1], self.install_dir,)
+ %s
+ """).lstrip()
- if not os.path.exists(self.install_dir):
- msg += """
-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).
-"""
- else:
- msg += """
-Perhaps your account does not have write access to this directory? If the
-installation directory is a system-owned directory, you may need to sign in
-as the administrator or "root" account. If you do not have administrative
-access to this machine, you may wish to choose a different installation
-directory, preferably one that is listed in your PYTHONPATH environment
-variable.
+ __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()
-For information on other options, you may wish to consult the
-documentation at:
+ __access_msg = textwrap.dedent("""
+ Perhaps your account does not have write access to this directory? If the
+ installation directory is a system-owned directory, you may need to sign in
+ as the administrator or "root" account. If you do not have administrative
+ access to this machine, you may wish to choose a different installation
+ directory, preferably one that is listed in your PYTHONPATH environment
+ variable.
- https://pythonhosted.org/setuptools/easy_install.html
+ For information on other options, you may wish to consult the
+ documentation at:
-Please make the appropriate changes for your system and try again.
-"""
+ https://pythonhosted.org/setuptools/easy_install.html
+
+ Please make the appropriate changes for your system and try again.
+ """).lstrip()
+
+ def cant_write_to_target(self):
+ msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,)
+
+ if not os.path.exists(self.install_dir):
+ msg += '\n' + self.__not_exists_id
+ else:
+ msg += '\n' + self.__access_msg
raise DistutilsError(msg)
def check_pth_processing(self):
@@ -742,7 +745,7 @@ Please make the appropriate changes for your system and try again.
def install_wrapper_scripts(self, dist):
if not self.exclude_scripts:
- for args in ScriptWriter.get_args(dist):
+ for args in ScriptWriter.best().get_args(dist):
self.write_script(*args)
def install_script(self, dist, script_name, script_text, dev_path=None):
@@ -980,46 +983,52 @@ Please make the appropriate changes for your system and try again.
f.write('\n'.join(locals()[name]) + '\n')
f.close()
+ __mv_warning = textwrap.dedent("""
+ Because this distribution was installed --multi-version, before you can
+ import modules from this package in an application, you will need to
+ 'import pkg_resources' and then use a 'require()' call similar to one of
+ these examples, in order to select the desired version:
+
+ pkg_resources.require("%(name)s") # latest installed version
+ pkg_resources.require("%(name)s==%(version)s") # this exact version
+ pkg_resources.require("%(name)s>=%(version)s") # this version or higher
+ """).lstrip()
+
+ __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.)
+ """)
+
def installation_report(self, req, dist, what="Installed"):
"""Helpful installation message for display to package users"""
msg = "\n%(what)s %(eggloc)s%(extras)s"
if self.multi_version and not self.no_report:
- msg += """
-
-Because this distribution was installed --multi-version, before you can
-import modules from this package in an application, you will need to
-'import pkg_resources' and then use a 'require()' call similar to one of
-these examples, in order to select the desired version:
-
- pkg_resources.require("%(name)s") # latest installed version
- pkg_resources.require("%(name)s==%(version)s") # this exact version
- pkg_resources.require("%(name)s>=%(version)s") # this version or higher
-"""
+ msg += '\n' + self.__mv_warning
if self.install_dir not in map(normalize_path, sys.path):
- msg += """
+ msg += '\n' + self.__id_warning
-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.)
-"""
eggloc = dist.location
name = dist.project_name
version = dist.version
extras = '' # TODO: self.report_extras(req, dist)
return msg % locals()
- def report_editable(self, spec, setup_script):
- dirname = os.path.dirname(setup_script)
- python = sys.executable
- return """\nExtracted editable version of %(spec)s to %(dirname)s
+ __editable_msg = textwrap.dedent("""
+ Extracted editable version of %(spec)s to %(dirname)s
-If it uses setuptools in its setup script, you can activate it in
-"development" mode by going to that directory and running::
+ If it uses setuptools in its setup script, you can activate it in
+ "development" mode by going to that directory and running::
- %(python)s setup.py develop
+ %(python)s setup.py develop
-See the setuptools documentation for the "develop" command for more info.
-""" % locals()
+ See the setuptools documentation for the "develop" command for more info.
+ """).lstrip()
+
+ def report_editable(self, spec, setup_script):
+ dirname = os.path.dirname(setup_script)
+ python = sys.executable
+ return '\n' + self.__editable_msg % locals()
def run_setup(self, setup_script, setup_base, args):
sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
@@ -1170,35 +1179,38 @@ See the setuptools documentation for the "develop" command for more info.
finally:
log.set_verbosity(self.verbose) # restore original verbosity
- def no_default_version_msg(self):
- template = """bad install directory or PYTHONPATH
+ __no_default_msg = textwrap.dedent("""
+ bad install directory or PYTHONPATH
+
+ You are attempting to install a package to a directory that is not
+ on PYTHONPATH and which Python does not read ".pth" files from. The
+ installation directory you specified (via --install-dir, --prefix, or
+ the distutils default setting) was:
-You are attempting to install a package to a directory that is not
-on PYTHONPATH and which Python does not read ".pth" files from. The
-installation directory you specified (via --install-dir, --prefix, or
-the distutils default setting) was:
+ %s
- %s
+ and your PYTHONPATH environment variable currently contains:
-and your PYTHONPATH environment variable currently contains:
+ %r
- %r
+ Here are some of your options for correcting the problem:
-Here are some of your options for correcting the problem:
+ * You can choose a different installation directory, i.e., one that is
+ on PYTHONPATH or supports .pth files
-* You can choose a different installation directory, i.e., one that is
- on PYTHONPATH or supports .pth files
+ * You can add the installation directory to the PYTHONPATH environment
+ variable. (It must then also be on PYTHONPATH whenever you run
+ Python and want to use the package(s) you are installing.)
-* You can add the installation directory to the PYTHONPATH environment
- variable. (It must then also be on PYTHONPATH whenever you run
- Python and want to use the package(s) you are installing.)
+ * You can set up the installation directory to support ".pth" files by
+ using one of the approaches described here:
-* You can set up the installation directory to support ".pth" files by
- using one of the approaches described here:
+ https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations
- https://pythonhosted.org/setuptools/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."""
+ 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):
@@ -1398,13 +1410,8 @@ def extract_wininst_cfg(dist_filename):
{'version': '', 'target_version': ''})
try:
part = f.read(cfglen)
- # part is in bytes, but we need to read up to the first null
- # byte.
- if sys.version_info >= (2, 6):
- null_byte = bytes([0])
- else:
- null_byte = chr(0)
- config = part.split(null_byte, 1)[0]
+ # Read up to the first null byte.
+ config = part.split(b'\0', 1)[0]
# Now the config is in bytes, but for RawConfigParser, it should
# be text, so decode it.
config = config.decode(sys.getfilesystemencoding())
@@ -1787,9 +1794,8 @@ def is_python(text, filename='<string>'):
def is_sh(executable):
"""Determine if the specified executable is a .sh (contains a #! line)"""
try:
- fp = open(executable)
- magic = fp.read(2)
- fp.close()
+ with io.open(executable, encoding='latin-1') as fp:
+ magic = fp.read(2)
except (OSError, IOError):
return executable
return magic == '#!'
@@ -1831,25 +1837,14 @@ def chmod(path, mode):
def fix_jython_executable(executable, options):
- if sys.platform.startswith('java') and is_sh(executable):
- # Workaround for Jython is not needed on Linux systems.
- import java
+ warnings.warn("Use JythonCommandSpec", DeprecationWarning, stacklevel=2)
- if java.lang.System.getProperty("os.name") == "Linux":
- return executable
+ if not JythonCommandSpec.relevant():
+ return executable
- # Workaround Jython's sys.executable being a .sh (an invalid
- # shebang line interpreter)
- if options:
- # Can't apply the workaround, leave it broken
- log.warn(
- "WARNING: Unable to adapt shebang line for Jython,"
- " the following script is NOT executable\n"
- " see http://bugs.jython.org/issue1112 for"
- " more information.")
- else:
- return '/usr/bin/env %s' % executable
- return executable
+ cmd = CommandSpec.best().from_param(executable)
+ cmd.install_options(options)
+ return cmd.as_header().lstrip('#!').rstrip('\n')
class CommandSpec(list):
@@ -1859,6 +1854,14 @@ class CommandSpec(list):
"""
options = []
+ split_args = dict()
+
+ @classmethod
+ def best(cls):
+ """
+ Choose the best CommandSpec class based on environmental conditions.
+ """
+ return cls if not JythonCommandSpec.relevant() else JythonCommandSpec
@classmethod
def _sys_executable(cls):
@@ -1882,7 +1885,7 @@ class CommandSpec(list):
@classmethod
def from_environment(cls):
- return cls.from_string('"' + cls._sys_executable() + '"')
+ return cls([cls._sys_executable()])
@classmethod
def from_string(cls, string):
@@ -1890,8 +1893,8 @@ class CommandSpec(list):
Construct a command spec from a simple string representing a command
line parseable by shlex.split.
"""
- items = shlex.split(string)
- return JythonCommandSpec.from_string(string) or cls(items)
+ items = shlex.split(string, **cls.split_args)
+ return cls(items)
def install_options(self, script_text):
self.options = shlex.split(self._extract_options(script_text))
@@ -1917,20 +1920,31 @@ class CommandSpec(list):
cmdline = subprocess.list2cmdline(items)
return '#!' + cmdline + '\n'
+# For pbr compat; will be removed in a future version.
+sys_executable = CommandSpec._sys_executable()
+
+
+class WindowsCommandSpec(CommandSpec):
+ split_args = dict(posix=False)
+
class JythonCommandSpec(CommandSpec):
@classmethod
- def from_string(cls, string):
- """
- On Jython, construct an instance of this class.
- On platforms other than Jython, return None.
- """
- needs_jython_spec = (
+ def relevant(cls):
+ return (
sys.platform.startswith('java')
and
__import__('java').lang.System.getProperty('os.name') != 'Linux'
)
- return cls([string]) if needs_jython_spec else None
+
+ @classmethod
+ def from_environment(cls):
+ string = '"' + cls._sys_executable() + '"'
+ return cls.from_string(string)
+
+ @classmethod
+ def from_string(cls, string):
+ return cls([string])
def as_header(self):
"""
@@ -1971,11 +1985,13 @@ class ScriptWriter(object):
)
""").lstrip()
+ command_spec_class = CommandSpec
+
@classmethod
def get_script_args(cls, dist, executable=None, wininst=False):
# for backward compatibility
warnings.warn("Use get_args", DeprecationWarning)
- writer = cls.get_writer(wininst)
+ writer = (WindowsScriptWriter if wininst else ScriptWriter).best()
header = cls.get_script_header("", executable, wininst)
return writer.get_args(dist, header)
@@ -1985,7 +2001,7 @@ class ScriptWriter(object):
warnings.warn("Use get_header", DeprecationWarning)
if wininst:
executable = "python.exe"
- cmd = CommandSpec.from_param(executable)
+ cmd = cls.command_spec_class.best().from_param(executable)
cmd.install_options(script_text)
return cmd.as_header()
@@ -2007,9 +2023,16 @@ class ScriptWriter(object):
@classmethod
def get_writer(cls, force_windows):
- if force_windows or sys.platform == 'win32':
- return WindowsScriptWriter.get_writer()
- return cls
+ # for backward compatibility
+ warnings.warn("Use best", DeprecationWarning)
+ return WindowsScriptWriter.best() if force_windows else cls.best()
+
+ @classmethod
+ def best(cls):
+ """
+ Select the best ScriptWriter for this environment.
+ """
+ return WindowsScriptWriter.best() if sys.platform == 'win32' else cls
@classmethod
def _get_script_args(cls, type_, name, header, script_text):
@@ -2019,16 +2042,24 @@ class ScriptWriter(object):
@classmethod
def get_header(cls, script_text="", executable=None):
"""Create a #! line, getting options (if any) from script_text"""
- cmd = CommandSpec.from_param(executable)
+ cmd = cls.command_spec_class.best().from_param(executable)
cmd.install_options(script_text)
return cmd.as_header()
class WindowsScriptWriter(ScriptWriter):
+ command_spec_class = WindowsCommandSpec
+
@classmethod
def get_writer(cls):
+ # for backward compatibility
+ warnings.warn("Use best", DeprecationWarning)
+ return cls.best()
+
+ @classmethod
+ def best(cls):
"""
- Get a script writer suitable for Windows
+ Select the best ScriptWriter suitable for Windows
"""
writer_lookup = dict(
executable=WindowsExecutableLauncherWriter,
@@ -2225,4 +2256,3 @@ def _patch_usage():
yield
finally:
distutils.core.gen_usage = saved
-
diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py
index 722b0566..be66cb22 100755
--- a/setuptools/command/install_scripts.py
+++ b/setuptools/command/install_scripts.py
@@ -13,7 +13,7 @@ class install_scripts(orig.install_scripts):
self.no_ep = False
def run(self):
- from setuptools.command.easy_install import ScriptWriter, CommandSpec
+ import setuptools.command.easy_install as ei
self.run_command("egg_info")
if self.distribution.scripts:
@@ -30,13 +30,16 @@ class install_scripts(orig.install_scripts):
ei_cmd.egg_name, ei_cmd.egg_version,
)
bs_cmd = self.get_finalized_command('build_scripts')
- cmd = CommandSpec.from_param(getattr(bs_cmd, 'executable', None))
- is_wininst = getattr(
- self.get_finalized_command("bdist_wininst"), '_is_running', False
- )
+ exec_param = getattr(bs_cmd, 'executable', None)
+ bw_cmd = self.get_finalized_command("bdist_wininst")
+ is_wininst = getattr(bw_cmd, '_is_running', False)
+ writer = ei.ScriptWriter
if is_wininst:
- cmd = CommandSpec.from_string("python.exe")
- writer = ScriptWriter.get_writer(force_windows=is_wininst)
+ exec_param = "python.exe"
+ writer = ei.WindowsScriptWriter
+ # resolve the writer to the environment
+ writer = writer.best()
+ cmd = writer.command_spec_class.best().from_param(exec_param)
for args in writer.get_args(dist, cmd.as_header()):
self.write_script(*args)
diff --git a/setuptools/dist.py b/setuptools/dist.py
index bc29b131..ffbc7c48 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -277,10 +277,9 @@ class Distribution(_Distribution):
normalized_version = str(ver)
if self.metadata.version != normalized_version:
warnings.warn(
- "The version specified requires normalization, "
- "consider using '%s' instead of '%s'." % (
- normalized_version,
+ "Normalizing '%s' to '%s'" % (
self.metadata.version,
+ normalized_version,
)
)
self.metadata.version = normalized_version
diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py
index e76d70f0..a69c7474 100644
--- a/setuptools/msvc9_support.py
+++ b/setuptools/msvc9_support.py
@@ -1,5 +1,3 @@
-import sys
-
try:
import distutils.msvc9compiler
except ImportError:
@@ -29,13 +27,15 @@ def patch_for_specialized_compiler():
def find_vcvarsall(version):
Reg = distutils.msvc9compiler.Reg
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(VC_BASE % ('', version), "installdir")
+ productdir = Reg.get_value(key, "installdir")
except KeyError:
try:
# All-user installs on a 64-bit system register here
- productdir = Reg.get_value(VC_BASE % ('Wow6432Node\\', version), "installdir")
+ key = VC_BASE % ('Wow6432Node\\', version)
+ productdir = Reg.get_value(key, "installdir")
except KeyError:
productdir = None
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index 7971f42c..67255123 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -47,8 +47,10 @@ def _execfile(filename, globals, locals=None):
@contextlib.contextmanager
-def save_argv():
+def save_argv(repl=None):
saved = sys.argv[:]
+ if repl is not None:
+ sys.argv[:] = repl
try:
yield saved
finally:
@@ -92,6 +94,51 @@ def pushd(target):
os.chdir(saved)
+class UnpickleableException(Exception):
+ """
+ An exception representing another Exception that could not be pickled.
+ """
+ @classmethod
+ def dump(cls, type, exc):
+ """
+ Always return a dumped (pickled) type and exc. If exc can't be pickled,
+ wrap it in UnpickleableException first.
+ """
+ try:
+ return pickle.dumps(type), pickle.dumps(exc)
+ except Exception:
+ return cls.dump(cls, cls(repr(exc)))
+
+
+class ExceptionSaver:
+ """
+ A Context Manager that will save an exception, serialized, and restore it
+ later.
+ """
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, exc, tb):
+ if not exc:
+ return
+
+ # dump the exception
+ self._saved = UnpickleableException.dump(type, exc)
+ self._tb = tb
+
+ # suppress the exception
+ return True
+
+ def resume(self):
+ "restore and re-raise any exception"
+
+ if '_saved' not in vars(self):
+ return
+
+ type, exc = map(pickle.loads, self._saved)
+ compat.reraise(type, exc, self._tb)
+
+
@contextlib.contextmanager
def save_modules():
"""
@@ -101,31 +148,20 @@ def save_modules():
outside the context.
"""
saved = sys.modules.copy()
- try:
- try:
- yield saved
- except:
- # dump any exception
- class_, exc, tb = sys.exc_info()
- saved_cls = pickle.dumps(class_)
- saved_exc = pickle.dumps(exc)
- raise
- finally:
- sys.modules.update(saved)
- # remove any modules imported since
- del_modules = (
- mod_name for mod_name in sys.modules
- if mod_name not in saved
- # exclude any encodings modules. See #285
- and not mod_name.startswith('encodings.')
- )
- _clear_modules(del_modules)
- except:
- # reload and re-raise any exception, using restored modules
- class_, exc, tb = sys.exc_info()
- new_cls = pickle.loads(saved_cls)
- new_exc = pickle.loads(saved_exc)
- compat.reraise(new_cls, new_exc, tb)
+ with ExceptionSaver() as saved_exc:
+ yield saved
+
+ sys.modules.update(saved)
+ # remove any modules imported since
+ del_modules = (
+ mod_name for mod_name in sys.modules
+ if mod_name not in saved
+ # exclude any encodings modules. See #285
+ and not mod_name.startswith('encodings.')
+ )
+ _clear_modules(del_modules)
+
+ saved_exc.resume()
def _clear_modules(module_names):
diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py
index d06a333f..1d29284b 100644
--- a/setuptools/tests/contexts.py
+++ b/setuptools/tests/contexts.py
@@ -27,7 +27,7 @@ def environment(**replacements):
to clear the values.
"""
saved = dict(
- (key, os.environ['key'])
+ (key, os.environ[key])
for key in replacements
if key in os.environ
)
@@ -49,14 +49,6 @@ def environment(**replacements):
@contextlib.contextmanager
-def argv(repl):
- old_argv = sys.argv[:]
- sys.argv[:] = repl
- yield
- sys.argv[:] = old_argv
-
-
-@contextlib.contextmanager
def quiet():
"""
Redirect stdout/stderr to StringIO objects to prevent console output from
diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py
index 0b1eaf5f..c70c38cb 100644
--- a/setuptools/tests/fixtures.py
+++ b/setuptools/tests/fixtures.py
@@ -1,4 +1,7 @@
-import mock
+try:
+ from unittest import mock
+except ImportError:
+ import mock
import pytest
from . import contexts
diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py
index c53b4809..c5680881 100644
--- a/setuptools/tests/py26compat.py
+++ b/setuptools/tests/py26compat.py
@@ -8,4 +8,7 @@ def _tarfile_open_ex(*args, **kwargs):
"""
return contextlib.closing(tarfile.open(*args, **kwargs))
-tarfile_open = _tarfile_open_ex if sys.version_info < (2,7) else tarfile.open
+if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 2):
+ tarfile_open = _tarfile_open_ex
+else:
+ tarfile_open = tarfile.open
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 72b040e1..7d61fb83 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -1,4 +1,4 @@
-#! -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
"""Easy install Tests
"""
@@ -13,18 +13,19 @@ import contextlib
import tarfile
import logging
import itertools
+import distutils.errors
import pytest
-import mock
+try:
+ from unittest import mock
+except ImportError:
+ import mock
from setuptools import sandbox
from setuptools import compat
from setuptools.compat import StringIO, BytesIO, urlparse
from setuptools.sandbox import run_setup
-from setuptools.command.easy_install import (
- easy_install, fix_jython_executable, nt_quote_arg,
- is_sh, ScriptWriter, CommandSpec,
-)
+import setuptools.command.easy_install as ei
from setuptools.command.easy_install import PthDistributions
from setuptools.command import easy_install as easy_install_pkg
from setuptools.dist import Distribution
@@ -47,19 +48,6 @@ class FakeDist(object):
def as_requirement(self):
return 'spec'
-WANTED = DALS("""
- #!%s
- # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
- __requires__ = 'spec'
- import sys
- from pkg_resources import load_entry_point
-
- if __name__ == '__main__':
- sys.exit(
- load_entry_point('spec', 'console_scripts', 'name')()
- )
- """) % nt_quote_arg(fix_jython_executable(sys.executable, ""))
-
SETUP_PY = DALS("""
from setuptools import setup
@@ -70,7 +58,7 @@ class TestEasyInstallTest:
def test_install_site_py(self):
dist = Distribution()
- cmd = easy_install(dist)
+ cmd = ei.easy_install(dist)
cmd.sitepy_installed = False
cmd.install_dir = tempfile.mkdtemp()
try:
@@ -81,18 +69,30 @@ class TestEasyInstallTest:
shutil.rmtree(cmd.install_dir)
def test_get_script_args(self):
+ header = ei.CommandSpec.best().from_environment().as_header()
+ expected = header + DALS("""
+ # EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
+ __requires__ = 'spec'
+ import sys
+ from pkg_resources import load_entry_point
+
+ if __name__ == '__main__':
+ sys.exit(
+ load_entry_point('spec', 'console_scripts', 'name')()
+ )
+ """)
dist = FakeDist()
- args = next(ScriptWriter.get_args(dist))
+ args = next(ei.ScriptWriter.get_args(dist))
name, script = itertools.islice(args, 2)
- assert script == WANTED
+ assert script == expected
def test_no_find_links(self):
# new option '--no-find-links', that blocks find-links added at
# the project level
dist = Distribution()
- cmd = easy_install(dist)
+ cmd = ei.easy_install(dist)
cmd.check_pth_processing = lambda: True
cmd.no_find_links = True
cmd.find_links = ['link1', 'link2']
@@ -102,7 +102,7 @@ class TestEasyInstallTest:
assert cmd.package_index.scanned_urls == {}
# let's try without it (default behavior)
- cmd = easy_install(dist)
+ cmd = ei.easy_install(dist)
cmd.check_pth_processing = lambda: True
cmd.find_links = ['link1', 'link2']
cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
@@ -111,6 +111,16 @@ class TestEasyInstallTest:
keys = sorted(cmd.package_index.scanned_urls.keys())
assert keys == ['link1', 'link2']
+ def test_write_exception(self):
+ """
+ Test that `cant_write_to_target` is rendered as a DistutilsError.
+ """
+ dist = Distribution()
+ cmd = ei.easy_install(dist)
+ cmd.install_dir = os.getcwd()
+ with pytest.raises(distutils.errors.DistutilsError):
+ cmd.cant_write_to_target()
+
class TestPTHFileWriter:
def test_add_from_cwd_site_sets_dirty(self):
@@ -144,77 +154,64 @@ def setup_context(tmpdir):
@pytest.mark.usefixtures("setup_context")
class TestUserInstallTest:
- @mock.patch('setuptools.command.easy_install.__file__', None)
- def test_user_install_implied(self):
- easy_install_pkg.__file__ = site.USER_SITE
- site.ENABLE_USER_SITE = True # disabled sometimes
- #XXX: replace with something meaningfull
+ # simulate setuptools installed in user site packages
+ @mock.patch('setuptools.command.easy_install.__file__', site.USER_SITE)
+ @mock.patch('site.ENABLE_USER_SITE', True)
+ def test_user_install_not_implied_user_site_enabled(self):
+ self.assert_not_user_site()
+
+ @mock.patch('site.ENABLE_USER_SITE', False)
+ def test_user_install_not_implied_user_site_disabled(self):
+ self.assert_not_user_site()
+
+ @staticmethod
+ def assert_not_user_site():
+ # create a finalized easy_install command
dist = Distribution()
dist.script_name = 'setup.py'
- cmd = easy_install(dist)
+ cmd = ei.easy_install(dist)
cmd.args = ['py']
cmd.ensure_finalized()
- assert cmd.user, 'user should be implied'
+ assert not cmd.user, 'user should not be implied'
def test_multiproc_atexit(self):
- try:
- __import__('multiprocessing')
- except ImportError:
- # skip the test if multiprocessing is not available
- return
+ pytest.importorskip('multiprocessing')
log = logging.getLogger('test_easy_install')
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
log.info('this should not break')
- def test_user_install_not_implied_without_usersite_enabled(self):
- site.ENABLE_USER_SITE = False # usually enabled
- #XXX: replace with something meaningfull
- dist = Distribution()
- dist.script_name = 'setup.py'
- cmd = easy_install(dist)
- cmd.args = ['py']
- cmd.initialize_options()
- assert not cmd.user, 'NOT user should be implied'
-
- def test_local_index(self):
- # make sure the local index is used
- # when easy_install looks for installed
- # packages
- new_location = tempfile.mkdtemp()
- target = tempfile.mkdtemp()
- egg_file = os.path.join(new_location, 'foo-1.0.egg-info')
- with open(egg_file, 'w') as f:
+ @pytest.fixture()
+ def foo_package(self, tmpdir):
+ egg_file = tmpdir / 'foo-1.0.egg-info'
+ with egg_file.open('w') as f:
f.write('Name: foo\n')
+ return str(tmpdir)
- sys.path.append(target)
- old_ppath = os.environ.get('PYTHONPATH')
- os.environ['PYTHONPATH'] = os.path.pathsep.join(sys.path)
- try:
- dist = Distribution()
- dist.script_name = 'setup.py'
- cmd = easy_install(dist)
- cmd.install_dir = target
- cmd.args = ['foo']
- cmd.ensure_finalized()
- cmd.local_index.scan([new_location])
- res = cmd.easy_install('foo')
- actual = os.path.normcase(os.path.realpath(res.location))
- expected = os.path.normcase(os.path.realpath(new_location))
- assert actual == expected
- finally:
- sys.path.remove(target)
- for basedir in [new_location, target, ]:
- if not os.path.exists(basedir) or not os.path.isdir(basedir):
- continue
- try:
- shutil.rmtree(basedir)
- except:
- pass
- if old_ppath is not None:
- os.environ['PYTHONPATH'] = old_ppath
- else:
- del os.environ['PYTHONPATH']
+ @pytest.yield_fixture()
+ def install_target(self, tmpdir):
+ target = str(tmpdir)
+ with mock.patch('sys.path', sys.path + [target]):
+ python_path = os.path.pathsep.join(sys.path)
+ with mock.patch.dict(os.environ, PYTHONPATH=python_path):
+ yield target
+
+ def test_local_index(self, foo_package, install_target):
+ """
+ The local index must be used when easy_install locates installed
+ packages.
+ """
+ dist = Distribution()
+ dist.script_name = 'setup.py'
+ cmd = ei.easy_install(dist)
+ cmd.install_dir = install_target
+ cmd.args = ['foo']
+ cmd.ensure_finalized()
+ cmd.local_index.scan([foo_package])
+ res = cmd.easy_install('foo')
+ actual = os.path.normcase(os.path.realpath(res.location))
+ expected = os.path.normcase(os.path.realpath(foo_package))
+ assert actual == expected
@contextlib.contextmanager
def user_install_setup_context(self, *args, **kwargs):
@@ -302,7 +299,7 @@ class TestSetupRequires:
'--install-dir', temp_install_dir,
dist_file,
]
- with contexts.argv(['easy_install']):
+ with sandbox.save_argv(['easy_install']):
# attempt to install the dist. It should fail because
# it doesn't exist.
with pytest.raises(SystemExit):
@@ -420,24 +417,25 @@ class TestScriptHeader:
exe_with_spaces = r'C:\Program Files\Python33\python.exe'
@pytest.mark.skipif(
- sys.platform.startswith('java') and is_sh(sys.executable),
+ sys.platform.startswith('java') and ei.is_sh(sys.executable),
reason="Test cannot run under java when executable is sh"
)
def test_get_script_header(self):
- expected = '#!%s\n' % nt_quote_arg(os.path.normpath(sys.executable))
- actual = ScriptWriter.get_script_header('#!/usr/local/bin/python')
+ expected = '#!%s\n' % ei.nt_quote_arg(os.path.normpath(sys.executable))
+ actual = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python')
assert actual == expected
- expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable))
- actual = ScriptWriter.get_script_header('#!/usr/bin/python -x')
+ expected = '#!%s -x\n' % ei.nt_quote_arg(os.path.normpath
+ (sys.executable))
+ actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x')
assert actual == expected
- actual = ScriptWriter.get_script_header('#!/usr/bin/python',
+ actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
executable=self.non_ascii_exe)
expected = '#!%s -x\n' % self.non_ascii_exe
assert actual == expected
- actual = ScriptWriter.get_script_header('#!/usr/bin/python',
+ actual = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
executable='"'+self.exe_with_spaces+'"')
expected = '#!"%s"\n' % self.exe_with_spaces
assert actual == expected
@@ -460,7 +458,7 @@ class TestScriptHeader:
f.write(header)
exe = str(exe)
- header = ScriptWriter.get_script_header('#!/usr/local/bin/python',
+ header = ei.ScriptWriter.get_script_header('#!/usr/local/bin/python',
executable=exe)
assert header == '#!/usr/bin/env %s\n' % exe
@@ -469,14 +467,14 @@ class TestScriptHeader:
with contexts.quiet() as (stdout, stderr):
# When options are included, generate a broken shebang line
# with a warning emitted
- candidate = ScriptWriter.get_script_header('#!/usr/bin/python -x',
+ candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python -x',
executable=exe)
assert candidate == '#!%s -x\n' % exe
output = locals()[expect_out]
assert 'Unable to adapt shebang line' in output.getvalue()
with contexts.quiet() as (stdout, stderr):
- candidate = ScriptWriter.get_script_header('#!/usr/bin/python',
+ candidate = ei.ScriptWriter.get_script_header('#!/usr/bin/python',
executable=self.non_ascii_exe)
assert candidate == '#!%s -x\n' % self.non_ascii_exe
output = locals()[expect_out]
@@ -489,20 +487,20 @@ class TestCommandSpec:
Show how a custom CommandSpec could be used to specify a #! executable
which takes parameters.
"""
- cmd = CommandSpec(['/usr/bin/env', 'python3'])
+ cmd = ei.CommandSpec(['/usr/bin/env', 'python3'])
assert cmd.as_header() == '#!/usr/bin/env python3\n'
def test_from_param_for_CommandSpec_is_passthrough(self):
"""
from_param should return an instance of a CommandSpec
"""
- cmd = CommandSpec(['python'])
- cmd_new = CommandSpec.from_param(cmd)
+ cmd = ei.CommandSpec(['python'])
+ cmd_new = ei.CommandSpec.from_param(cmd)
assert cmd is cmd_new
def test_from_environment_with_spaces_in_executable(self):
with mock.patch('sys.executable', TestScriptHeader.exe_with_spaces):
- cmd = CommandSpec.from_environment()
+ cmd = ei.CommandSpec.from_environment()
assert len(cmd) == 1
assert cmd.as_header().startswith('#!"')
@@ -511,6 +509,26 @@ class TestCommandSpec:
In order to support `executable = /usr/bin/env my-python`, make sure
from_param invokes shlex on that input.
"""
- cmd = CommandSpec.from_param('/usr/bin/env my-python')
+ cmd = ei.CommandSpec.from_param('/usr/bin/env my-python')
assert len(cmd) == 2
assert '"' not in cmd.as_header()
+
+ def test_sys_executable(self):
+ """
+ CommandSpec.from_string(sys.executable) should contain just that param.
+ """
+ writer = ei.ScriptWriter.best()
+ cmd = writer.command_spec_class.from_string(sys.executable)
+ assert len(cmd) == 1
+ assert cmd[0] == sys.executable
+
+
+class TestWindowsScriptWriter:
+ def test_header(self):
+ hdr = ei.WindowsScriptWriter.get_script_header('')
+ assert hdr.startswith('#!')
+ assert hdr.endswith('\n')
+ hdr = hdr.lstrip('#!')
+ hdr = hdr.rstrip('\n')
+ # header should not start with an escaped quote
+ assert not hdr.startswith('\\"')
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 92a27080..90bb4313 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -12,6 +12,7 @@ import pytest
from setuptools.command.easy_install import easy_install
from setuptools.command import easy_install as easy_install_pkg
from setuptools.dist import Distribution
+from setuptools.compat import urlopen
def setup_module(module):
@@ -24,6 +25,11 @@ def setup_module(module):
except ImportError:
pass
+ try:
+ urlopen('https://pypi.python.org/pypi')
+ except Exception as exc:
+ pytest.skip(reason=str(exc))
+
@pytest.fixture
def install_context(request, tmpdir, monkeypatch):
diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py
index a0820fff..09e0460c 100644
--- a/setuptools/tests/test_msvc9compiler.py
+++ b/setuptools/tests/test_msvc9compiler.py
@@ -7,7 +7,10 @@ import contextlib
import distutils.errors
import pytest
-import mock
+try:
+ from unittest import mock
+except ImportError:
+ import mock
from . import contexts
@@ -110,7 +113,8 @@ class TestModulePatch:
Ensure user's settings are preferred.
"""
result = distutils.msvc9compiler.find_vcvarsall(9.0)
- assert user_preferred_setting == result
+ expected = os.path.join(user_preferred_setting, 'vcvarsall.bat')
+ assert expected == result
@pytest.yield_fixture
def local_machine_setting(self):
@@ -131,13 +135,14 @@ class TestModulePatch:
Ensure machine setting is honored if user settings are not present.
"""
result = distutils.msvc9compiler.find_vcvarsall(9.0)
- assert local_machine_setting == result
+ expected = os.path.join(local_machine_setting, 'vcvarsall.bat')
+ assert expected == result
@pytest.yield_fixture
def x64_preferred_setting(self):
"""
Set up environment with 64-bit and 32-bit system settings configured
- and yield the 64-bit location.
+ and yield the canonical location.
"""
with self.mock_install_dir() as x32_dir:
with self.mock_install_dir() as x64_dir:
@@ -150,14 +155,15 @@ class TestModulePatch:
},
)
with reg:
- yield x64_dir
+ yield x32_dir
def test_ensure_64_bit_preferred(self, x64_preferred_setting):
"""
Ensure 64-bit system key is preferred.
"""
result = distutils.msvc9compiler.find_vcvarsall(9.0)
- assert x64_preferred_setting == result
+ expected = os.path.join(x64_preferred_setting, 'vcvarsall.bat')
+ assert expected == result
@staticmethod
@contextlib.contextmanager
@@ -170,4 +176,4 @@ class TestModulePatch:
vcvarsall = os.path.join(result, 'vcvarsall.bat')
with open(vcvarsall, 'w'):
pass
- yield
+ yield result
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index cadc4812..6e1e9e1c 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -7,7 +7,7 @@ import pytest
import pkg_resources
import setuptools.sandbox
-from setuptools.sandbox import DirectorySandbox, SandboxViolation
+from setuptools.sandbox import DirectorySandbox
class TestSandbox:
@@ -54,3 +54,49 @@ class TestSandbox:
with setup_py.open('wb') as stream:
stream.write(b'"degenerate script"\r\n')
setuptools.sandbox._execfile(str(setup_py), globals())
+
+
+class TestExceptionSaver:
+ def test_exception_trapped(self):
+ with setuptools.sandbox.ExceptionSaver():
+ raise ValueError("details")
+
+ def test_exception_resumed(self):
+ with setuptools.sandbox.ExceptionSaver() as saved_exc:
+ raise ValueError("details")
+
+ with pytest.raises(ValueError) as caught:
+ saved_exc.resume()
+
+ assert isinstance(caught.value, ValueError)
+ assert str(caught.value) == 'details'
+
+ def test_exception_reconstructed(self):
+ orig_exc = ValueError("details")
+
+ with setuptools.sandbox.ExceptionSaver() as saved_exc:
+ raise orig_exc
+
+ with pytest.raises(ValueError) as caught:
+ saved_exc.resume()
+
+ assert isinstance(caught.value, ValueError)
+ assert caught.value is not orig_exc
+
+ def test_no_exception_passes_quietly(self):
+ with setuptools.sandbox.ExceptionSaver() as saved_exc:
+ pass
+
+ saved_exc.resume()
+
+ def test_unpickleable_exception(self):
+ class CantPickleThis(Exception):
+ "This Exception is unpickleable because it's not in globals"
+
+ with setuptools.sandbox.ExceptionSaver() as saved_exc:
+ raise CantPickleThis('detail')
+
+ with pytest.raises(setuptools.sandbox.UnpickleableException) as caught:
+ saved_exc.resume()
+
+ assert str(caught.value) == "CantPickleThis('detail',)"
diff --git a/setuptools/version.py b/setuptools/version.py
index 1e71f92d..525a47ea 100644
--- a/setuptools/version.py
+++ b/setuptools/version.py
@@ -1 +1 @@
-__version__ = '11.3.2'
+__version__ = '13.0.2'