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 ...@@ -2,12 +2,66 @@ import sys
import re import re
import functools import functools
import distutils.core import distutils.core
import distutils.errors
import distutils.extension import distutils.extension
import distutils.msvc9compiler
from setuptools.dist import _get_unpatched from setuptools.dist import _get_unpatched
_Extension = _get_unpatched(distutils.core.Extension) _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(): def have_pyrex():
""" """
Return True if Cython or Pyrex can be imported. 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