summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgtags2
-rw-r--r--CHANGES.txt27
-rw-r--r--ez_setup.py2
-rwxr-xr-xsetup.cfg2
-rwxr-xr-xsetuptools/command/egg_info.py2
-rwxr-xr-xsetuptools/command/install_egg_info.py2
-rw-r--r--setuptools/command/install_lib.py83
-rw-r--r--setuptools/extension.py6
-rw-r--r--setuptools/msvc9_support.py64
-rw-r--r--setuptools/tests/test_msvc9compiler.py157
-rw-r--r--setuptools/version.py2
11 files changed, 325 insertions, 24 deletions
diff --git a/.hgtags b/.hgtags
index 17532221..ff59322b 100644
--- a/.hgtags
+++ b/.hgtags
@@ -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):
diff --git a/setup.cfg b/setup.cfg
index 7f5fc796..74a172a4 100755
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,5 +21,5 @@ formats = gztar zip
universal=1
[pytest]
-addopts=--ignore tests/manual_test.py --ignore tests/shlib_test
+addopts=--doctest-modules --ignore release.py --ignore setuptools/lib2to3_ex.py --ignore tests/manual_test.py --ignore tests/shlib_test
norecursedirs=dist build *.egg
diff --git a/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'