aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--setuptools/extension.py54
-rw-r--r--setuptools/tests/test_msvc9compiler.py135
2 files changed, 189 insertions, 0 deletions
diff --git a/setuptools/extension.py b/setuptools/extension.py
index ab5908da..41a817b7 100644
--- a/setuptools/extension.py
+++ b/setuptools/extension.py
@@ -2,12 +2,66 @@ import sys
import re
import functools
import distutils.core
+import distutils.errors
import distutils.extension
+import distutils.msvc9compiler
from setuptools.dist import _get_unpatched
_Extension = _get_unpatched(distutils.core.Extension)
+def _patch_msvc9compiler_find_vcvarsall():
+ """
+ Looks for the standalone VC for Python before falling back on
+ distutils's original approach.
+ """
+ VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+ find_vcvarsall = distutils.msvc9compiler.find_vcvarsall
+ query_vcvarsall = distutils.msvc9compiler.query_vcvarsall
+ if find_vcvarsall and find_vcvarsall.__module__.startswith('setuptools.'):
+ # Already patched
+ return
+
+ def _find_vcvarsall(version):
+ Reg = distutils.msvc9compiler.Reg
+ 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 find_vcvarsall(version)
+
+ def _query_vcvarsall(version, *args, **kwargs):
+ try:
+ return 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
+
+ distutils.msvc9compiler.find_vcvarsall = _find_vcvarsall
+ distutils.msvc9compiler.query_vcvarsall = _query_vcvarsall
+_patch_msvc9compiler_find_vcvarsall()
+
def have_pyrex():
"""
Return True if Cython or Pyrex can be imported.
diff --git a/setuptools/tests/test_msvc9compiler.py b/setuptools/tests/test_msvc9compiler.py
new file mode 100644
index 00000000..272e99e8
--- /dev/null
+++ b/setuptools/tests/test_msvc9compiler.py
@@ -0,0 +1,135 @@
+"""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
+
+try:
+ from winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE
+except ImportError:
+ from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE
+
+import distutils.msvc9compiler
+from distutils.errors import DistutilsPlatformError
+
+# 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
+
+ hives = {
+ HKEY_CURRENT_USER: self.hkcu,
+ 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
+
+class TestMSVC9Compiler(unittest.TestCase):
+
+ def test_find_vcvarsall_patch(self):
+ self.assertEqual(
+ "setuptools.extension",
+ 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
+ old_value = os.environ.pop("VS90COMNTOOLS", None)
+ try:
+ with MockReg():
+ self.assertIsNone(find_vcvarsall(9.0))
+
+ try:
+ query_vcvarsall(9.0)
+ self.fail('Expected DistutilsPlatformError from query_vcvarsall()')
+ except DistutilsPlatformError:
+ exc_message = str(sys.exc_info()[1])
+ self.assertIn('aka.ms/vcpython27', exc_message)
+ finally:
+ if old_value:
+ os.environ["VS90COMNTOOLS"] = old_value
+
+ 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)
+
+ \ No newline at end of file