diff options
| -rw-r--r-- | .hgtags | 2 | ||||
| -rw-r--r-- | CHANGES.txt | 27 | ||||
| -rw-r--r-- | ez_setup.py | 2 | ||||
| -rwxr-xr-x | setup.cfg | 2 | ||||
| -rwxr-xr-x | setuptools/command/egg_info.py | 2 | ||||
| -rwxr-xr-x | setuptools/command/install_egg_info.py | 2 | ||||
| -rw-r--r-- | setuptools/command/install_lib.py | 83 | ||||
| -rw-r--r-- | setuptools/extension.py | 6 | ||||
| -rw-r--r-- | setuptools/msvc9_support.py | 64 | ||||
| -rw-r--r-- | setuptools/tests/test_msvc9compiler.py | 157 | ||||
| -rw-r--r-- | setuptools/version.py | 2 |
11 files changed, 325 insertions, 24 deletions
@@ -154,3 +154,5 @@ ba3b08c7bffd6123e1a7d58994f15e8051a67cb7 5.4.1 a1fc0220bfa3581158688789f6dfdc00672eb99b 5.6 37ed55fd310d0cd32009dc5676121e86b404a23d 5.7 67550a8ed9f4ef49ee5a31f433adbf5a0eaeccf9 5.8 +755cbfd3743ffb186cdf7e20be8e61dbdaa22503 6.0 +bc6655b4acf205dd9f25c702955645656077398a 6.0.1 diff --git a/CHANGES.txt b/CHANGES.txt index e458d76c..3cba6353 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,21 @@ CHANGES ======= +--- +7.0 +--- + +* Implement PEP 440 within pkg_resources and setuptools. This will cause some + versions to no longer be installable without using the ``===`` escape hatch. + +----- +6.0.1 +----- + +* Issue #259: Fixed regression with namespace package handling on ``single + version, externally managed`` installs. + +--- 6.0 --- @@ -22,9 +37,15 @@ CHANGES Any users producing distributions with filenames that match those above case-insensitively, but not case-sensitively, should rename those files in their repository for better portability. - -* Implement PEP 440 within pkg_resources and setuptools. This will cause some - versions to no longer be installable without using the ``===`` escape hatch. +* Pull Request #72: When using ``single_version_externally_managed``, the + exclusion list now includes Python 3.2 ``__pycache__`` entries. +* Pull Request #76 and Pull Request #78: lines in top_level.txt are now + ordered deterministically. +* Issue #118: The egg-info directory is now no longer included in the list + of outputs. +* Issue #258: Setuptools now patches distutils msvc9compiler to + recognize the specially-packaged compiler package for easy extension module + support on Python 2.6, 2.7, and 3.2. --- 5.8 diff --git a/ez_setup.py b/ez_setup.py index 0fd1c872..c5d85d37 100644 --- a/ez_setup.py +++ b/ez_setup.py @@ -36,7 +36,7 @@ try: except ImportError: USER_SITE = None -DEFAULT_VERSION = "5.9" +DEFAULT_VERSION = "6.0.2" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" def _python_cmd(*args): @@ -21,5 +21,5 @@ formats = gztar zip universal=1 [pytest] -addopts=--ignore tests/manual_test.py --ignore tests/shlib_test +addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/shlib_test norecursedirs=dist build *.egg diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index cb67255b..9ba719fe 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -395,7 +395,7 @@ def write_toplevel_names(cmd, basename, filename): for k in cmd.distribution.iter_distribution_names() ] ) - cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n') + cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') def overwrite_arg(cmd, basename, filename): diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py index fd0f118b..992709f1 100755 --- a/setuptools/command/install_egg_info.py +++ b/setuptools/command/install_egg_info.py @@ -27,7 +27,7 @@ class install_egg_info(Command): ).egg_name() + '.egg-info' self.source = ei_cmd.egg_info self.target = os.path.join(self.install_dir, basename) - self.outputs = [self.target] + self.outputs = [] def run(self): self.run_command('egg_info') diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py index d7e117f0..9b772227 100644 --- a/setuptools/command/install_lib.py +++ b/setuptools/command/install_lib.py @@ -1,6 +1,7 @@ -import distutils.command.install_lib as orig import os - +import imp +from itertools import product, starmap +import distutils.command.install_lib as orig class install_lib(orig.install_lib): """Don't add compiled flags to filenames of non-Python files""" @@ -13,19 +14,71 @@ class install_lib(orig.install_lib): self.byte_compile(outfiles) def get_exclusions(self): - exclude = {} - nsp = self.distribution.namespace_packages - svem = (nsp and self.get_finalized_command('install') - .single_version_externally_managed) - if svem: - for pkg in nsp: - parts = pkg.split('.') - while parts: - pkgdir = os.path.join(self.install_dir, *parts) - for f in '__init__.py', '__init__.pyc', '__init__.pyo': - exclude[os.path.join(pkgdir, f)] = 1 - parts.pop() - return exclude + """ + Return a collections.Sized collections.Container of paths to be + excluded for single_version_externally_managed installations. + """ + all_packages = ( + pkg + for ns_pkg in self._get_SVEM_NSPs() + for pkg in self._all_packages(ns_pkg) + ) + + excl_specs = product(all_packages, self._gen_exclusion_paths()) + return set(starmap(self._exclude_pkg_path, excl_specs)) + + def _exclude_pkg_path(self, pkg, exclusion_path): + """ + Given a package name and exclusion path within that package, + compute the full exclusion path. + """ + parts = pkg.split('.') + [exclusion_path] + return os.path.join(self.install_dir, *parts) + + @staticmethod + def _all_packages(pkg_name): + """ + >>> list(install_lib._all_packages('foo.bar.baz')) + ['foo.bar.baz', 'foo.bar', 'foo'] + """ + while pkg_name: + yield pkg_name + pkg_name, sep, child = pkg_name.rpartition('.') + + def _get_SVEM_NSPs(self): + """ + Get namespace packages (list) but only for + single_version_externally_managed installations and empty otherwise. + """ + # TODO: is it necessary to short-circuit here? i.e. what's the cost + # if get_finalized_command is called even when namespace_packages is + # False? + if not self.distribution.namespace_packages: + return [] + + install_cmd = self.get_finalized_command('install') + svem = install_cmd.single_version_externally_managed + + return self.distribution.namespace_packages if svem else [] + + @staticmethod + def _gen_exclusion_paths(): + """ + Generate file paths to be excluded for namespace packages (bytecode + cache files). + """ + # always exclude the package module itself + yield '__init__.py' + + yield '__init__.pyc' + yield '__init__.pyo' + + if not hasattr(imp, 'get_tag'): + return + + base = os.path.join('__pycache__', '__init__.' + imp.get_tag()) + yield base + '.pyc' + yield base + '.pyo' def copy_tree( self, infile, outfile, diff --git a/setuptools/extension.py b/setuptools/extension.py index ab5908da..8178ed33 100644 --- a/setuptools/extension.py +++ b/setuptools/extension.py @@ -2,12 +2,16 @@ import sys import re import functools import distutils.core +import distutils.errors import distutils.extension -from setuptools.dist import _get_unpatched +from .dist import _get_unpatched +from . import msvc9_support _Extension = _get_unpatched(distutils.core.Extension) +msvc9_support.patch_for_specialized_compiler() + def have_pyrex(): """ Return True if Cython or Pyrex can be imported. diff --git a/setuptools/msvc9_support.py b/setuptools/msvc9_support.py new file mode 100644 index 00000000..d0be70e2 --- /dev/null +++ b/setuptools/msvc9_support.py @@ -0,0 +1,64 @@ +import sys + +try: + import distutils.msvc9compiler +except ImportError: + pass + +unpatched = dict() + +def patch_for_specialized_compiler(): + """ + Patch functions in distutils.msvc9compiler to use the standalone compiler + build for Python (Windows only). Fall back to original behavior when the + standalone compiler is not available. + """ + if 'distutils' not in globals(): + # The module isn't available to be patched + return + + if unpatched: + # Already patched + return + + unpatched.update(vars(distutils.msvc9compiler)) + + distutils.msvc9compiler.find_vcvarsall = find_vcvarsall + distutils.msvc9compiler.query_vcvarsall = query_vcvarsall + +def find_vcvarsall(version): + Reg = distutils.msvc9compiler.Reg + VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' + try: + # Per-user installs register the compiler path here + productdir = Reg.get_value(VC_BASE % ('', version), "installdir") + except KeyError: + try: + # All-user installs on a 64-bit system register here + productdir = Reg.get_value(VC_BASE % ('Wow6432Node\\', version), "installdir") + except KeyError: + productdir = None + + if productdir: + import os + vcvarsall = os.path.join(productdir, "vcvarsall.bat") + if os.path.isfile(vcvarsall): + return vcvarsall + + return unpatched['find_vcvarsall'](version) + +def query_vcvarsall(version, *args, **kwargs): + try: + return unpatched['query_vcvarsall'](version, *args, **kwargs) + except distutils.errors.DistutilsPlatformError: + exc = sys.exc_info()[1] + if exc and "vcvarsall.bat" in exc.args[0]: + message = 'Microsoft Visual C++ %0.1f is required (%s).' % (version, exc.args[0]) + if int(version) == 9: + # This redirection link is maintained by Microsoft. + # Contact vspython@microsoft.com if it needs updating. + raise distutils.errors.DistutilsPlatformError( + message + ' Get it from http://aka.ms/vcpython27' + ) + raise distutils.errors.DistutilsPlatformError(message) + raise diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py new file mode 100644 index 00000000..970f7679 --- /dev/null +++ b/setuptools/tests/test_msvc9compiler.py @@ -0,0 +1,157 @@ +"""msvc9compiler monkey patch test + +This test ensures that importing setuptools is sufficient to replace +the standard find_vcvarsall function with our patched version that +finds the Visual C++ for Python package. +""" + +import os +import shutil +import sys +import tempfile +import unittest +import distutils.errors +import contextlib + +# importing only setuptools should apply the patch +__import__('setuptools') + +class MockReg: + """Mock for distutils.msvc9compiler.Reg. We patch it + with an instance of this class that mocks out the + functions that access the registry. + """ + + def __init__(self, hkey_local_machine={}, hkey_current_user={}): + self.hklm = hkey_local_machine + self.hkcu = hkey_current_user + + def __enter__(self): + self.original_read_keys = distutils.msvc9compiler.Reg.read_keys + self.original_read_values = distutils.msvc9compiler.Reg.read_values + + _winreg = getattr(distutils.msvc9compiler, '_winreg', None) + winreg = getattr(distutils.msvc9compiler, 'winreg', _winreg) + + hives = { + winreg.HKEY_CURRENT_USER: self.hkcu, + winreg.HKEY_LOCAL_MACHINE: self.hklm, + } + + def read_keys(cls, base, key): + """Return list of registry keys.""" + hive = hives.get(base, {}) + return [k.rpartition('\\')[2] + for k in hive if k.startswith(key.lower())] + + def read_values(cls, base, key): + """Return dict of registry keys and values.""" + hive = hives.get(base, {}) + return dict((k.rpartition('\\')[2], hive[k]) + for k in hive if k.startswith(key.lower())) + + distutils.msvc9compiler.Reg.read_keys = classmethod(read_keys) + distutils.msvc9compiler.Reg.read_values = classmethod(read_values) + + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + distutils.msvc9compiler.Reg.read_keys = self.original_read_keys + distutils.msvc9compiler.Reg.read_values = self.original_read_values + +@contextlib.contextmanager +def patch_env(**replacements): + """ + In a context, patch the environment with replacements. Pass None values + to clear the values. + """ + saved = dict( + (key, os.environ['key']) + for key in replacements + if key in os.environ + ) + + # remove values that are null + remove = (key for (key, value) in replacements.items() if value is None) + for key in list(remove): + os.environ.pop(key, None) + replacements.pop(key) + + os.environ.update(replacements) + + try: + yield saved + finally: + for key in replacements: + os.environ.pop(key, None) + os.environ.update(saved) + +class TestMSVC9Compiler(unittest.TestCase): + + def test_find_vcvarsall_patch(self): + if not hasattr(distutils, 'msvc9compiler'): + # skip + return + + self.assertEqual( + "setuptools.msvc9_support", + distutils.msvc9compiler.find_vcvarsall.__module__, + "find_vcvarsall was not patched" + ) + + find_vcvarsall = distutils.msvc9compiler.find_vcvarsall + query_vcvarsall = distutils.msvc9compiler.query_vcvarsall + + # No registry entries or environment variable means we should + # not find anything + with patch_env(VS90COMNTOOLS=None): + with MockReg(): + self.assertIsNone(find_vcvarsall(9.0)) + + try: + query_vcvarsall(9.0) + self.fail('Expected DistutilsPlatformError from query_vcvarsall()') + except distutils.errors.DistutilsPlatformError: + exc_message = str(sys.exc_info()[1]) + self.assertIn('aka.ms/vcpython27', exc_message) + + key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir' + key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir' + + # Make two mock files so we can tell whether HCKU entries are + # preferred to HKLM entries. + mock_installdir_1 = tempfile.mkdtemp() + mock_vcvarsall_bat_1 = os.path.join(mock_installdir_1, 'vcvarsall.bat') + open(mock_vcvarsall_bat_1, 'w').close() + mock_installdir_2 = tempfile.mkdtemp() + mock_vcvarsall_bat_2 = os.path.join(mock_installdir_2, 'vcvarsall.bat') + open(mock_vcvarsall_bat_2, 'w').close() + try: + # Ensure we get the current user's setting first + with MockReg( + hkey_current_user={key_32: mock_installdir_1}, + hkey_local_machine={ + key_32: mock_installdir_2, + key_64: mock_installdir_2, + } + ): + self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0)) + + # Ensure we get the local machine value if it's there + with MockReg(hkey_local_machine={key_32: mock_installdir_2}): + self.assertEqual(mock_vcvarsall_bat_2, find_vcvarsall(9.0)) + + # Ensure we prefer the 64-bit local machine key + # (*not* the Wow6432Node key) + with MockReg( + hkey_local_machine={ + # This *should* only exist on 32-bit machines + key_32: mock_installdir_1, + # This *should* only exist on 64-bit machines + key_64: mock_installdir_2, + } + ): + self.assertEqual(mock_vcvarsall_bat_1, find_vcvarsall(9.0)) + finally: + shutil.rmtree(mock_installdir_1) + shutil.rmtree(mock_installdir_2) diff --git a/setuptools/version.py b/setuptools/version.py index 7244139e..c974945d 100644 --- a/setuptools/version.py +++ b/setuptools/version.py @@ -1 +1 @@ -__version__ = '5.9' +__version__ = '6.0.2' |
