Commit b0f93f3b authored by Steve Dower's avatar Steve Dower

Adds monkeypatching for msvc9compiler.find_vcvarsall() to look for a...

Adds monkeypatching for msvc9compiler.find_vcvarsall() to look for a standalone compiler installation and improves the error message for missing VC installation.
parent d42aea78
......@@ -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.
......
"""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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment