summaryrefslogtreecommitdiff
path: root/setuptools
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 /setuptools
parenta979a4494369abbe40fcf65738fc78fc5ae88d7d (diff)
parent4334bd87fdb0e60ed6e019ad2eb3ee103032d316 (diff)
downloadpython-setuptools-bitbucket-dbb11565d5880616866a608ce04f93b2b3efedd8.tar.gz
Merge pull request #25 from dhellmann/fix-tox
Fix tox settings so they work
Diffstat (limited to 'setuptools')
-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
15 files changed, 429 insertions, 311 deletions
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'