Variant of SF patch 614770: MSVC 7 support
distutils now looks for the compiler version in sys.version, falling
back to MSVC 6 if the version isn't listed (Python 2.2 and lower).
Add helper routines for reading the registry. Refactor many
module functions into methods of the compiler to avoid passing
lots of state as arguments.
diff --git a/Lib/distutils/msvccompiler.py b/Lib/distutils/msvccompiler.py
index e07a6d5..b4c890d 100644
--- a/Lib/distutils/msvccompiler.py
+++ b/Lib/distutils/msvccompiler.py
@@ -1,7 +1,8 @@
"""distutils.msvccompiler
Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio."""
+for the Microsoft Visual Studio.
+"""
# Written by Perry Stoll
# hacked by Robin Becker and Thomas Heller to do a better job of
@@ -12,7 +13,6 @@
__revision__ = "$Id$"
import sys, os, string
-from types import *
from distutils.errors import \
DistutilsExecError, DistutilsPlatformError, \
CompileError, LibError, LinkError
@@ -48,126 +48,116 @@
pass
if _can_read_reg:
- HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
- HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
- HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
- HKEY_USERS = hkey_mod.HKEY_USERS
+ HKEYS = (hkey_mod.HKEY_USERS,
+ hkey_mod.HKEY_CURRENT_USER,
+ hkey_mod.HKEY_LOCAL_MACHINE,
+ hkey_mod.HKEY_CLASSES_ROOT)
+def read_keys(base, key):
+ """Return list of registry keys."""
-
-def get_devstudio_versions ():
- """Get list of devstudio versions from the Windows registry. Return a
- list of strings containing version numbers; the list will be
- empty if we were unable to access the registry (eg. couldn't import
- a registry-access module) or the appropriate registry keys weren't
- found."""
-
- if not _can_read_reg:
- return []
-
- K = 'Software\\Microsoft\\Devstudio'
+ try:
+ handle = RegOpenKeyEx(base, key)
+ except RegError:
+ return None
L = []
- for base in (HKEY_CLASSES_ROOT,
- HKEY_LOCAL_MACHINE,
- HKEY_CURRENT_USER,
- HKEY_USERS):
+ i = 0
+ while 1:
try:
- k = RegOpenKeyEx(base,K)
- i = 0
- while 1:
- try:
- p = RegEnumKey(k,i)
- if p[0] in '123456789' and p not in L:
- L.append(p)
- except RegError:
- break
- i = i + 1
+ k = RegEnumKey(handle, i)
except RegError:
- pass
- L.sort()
- L.reverse()
+ break
+ L.append(k)
+ i = i + 1
return L
-# get_devstudio_versions ()
+def read_values(base, key):
+ """Return dict of registry keys and values.
-
-def get_msvc_paths (path, version='6.0', platform='x86'):
- """Get a list of devstudio directories (include, lib or path). Return
- a list of strings; will be empty list if unable to access the
- registry or appropriate registry keys not found."""
-
- if not _can_read_reg:
- return []
-
- L = []
- if path=='lib':
- path= 'Library'
- path = string.upper(path + ' Dirs')
- K = ('Software\\Microsoft\\Devstudio\\%s\\' +
- 'Build System\\Components\\Platforms\\Win32 (%s)\\Directories') % \
- (version,platform)
- for base in (HKEY_CLASSES_ROOT,
- HKEY_LOCAL_MACHINE,
- HKEY_CURRENT_USER,
- HKEY_USERS):
+ All names are converted to lowercase.
+ """
+ try:
+ handle = RegOpenKeyEx(base, key)
+ except RegError:
+ return None
+ d = {}
+ i = 0
+ while 1:
try:
- k = RegOpenKeyEx(base,K)
- i = 0
- while 1:
- try:
- (p,v,t) = RegEnumValue(k,i)
- if string.upper(p) == path:
- V = string.split(v,';')
- for v in V:
- if hasattr(v, "encode"):
- try:
- v = v.encode("mbcs")
- except UnicodeError:
- pass
- if v == '' or v in L: continue
- L.append(v)
- break
- i = i + 1
- except RegError:
- break
+ name, value, type = RegEnumValue(handle, i)
except RegError:
+ break
+ name = name.lower()
+ d[convert_mbcs(name)] = convert_mbcs(value)
+ i = i + 1
+ return d
+
+def convert_mbcs(s):
+ enc = getattr(s, "encode", None)
+ if enc is not None:
+ try:
+ s = enc("mbcs")
+ except UnicodeError:
pass
- return L
+ return s
-# get_msvc_paths()
+class MacroExpander:
+ def __init__(self, version):
+ self.macros = {}
+ self.load_macros(version)
-def find_exe (exe, version_number):
- """Try to find an MSVC executable program 'exe' (from version
- 'version_number' of MSVC) in several places: first, one of the MSVC
- program search paths from the registry; next, the directories in the
- PATH environment variable. If any of those work, return an absolute
- path that is known to exist. If none of them work, just return the
- original program name, 'exe'."""
+ def set_macro(self, macro, path, key):
+ for base in HKEYS:
+ d = read_values(base, path)
+ if d:
+ self.macros["$(%s)" % macro] = d[key]
+ break
+
+ def load_macros(self, version):
+ vsbase = r"Software\Microsoft\VisualStudio\%s.0" % version
+ self.set_macro("VCInstallDir", vsbase + r"\Setup\VC", "productdir")
+ self.set_macro("VSInstallDir", vsbase + r"\Setup\VS", "productdir")
+ net = r"Software\Microsoft\.NETFramework"
+ self.set_macro("FrameworkDir", net, "installroot")
+ self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
- for p in get_msvc_paths ('path', version_number):
- fn = os.path.join (os.path.abspath(p), exe)
- if os.path.isfile(fn):
- return fn
+ p = r"Software\Microsoft\NET Framework Setup\Product"
+ for base in HKEYS:
+ try:
+ h = RegOpenKeyEx(base, p)
+ except RegError:
+ continue
+ key = RegEnumKey(h, 0)
+ d = read_values(base, r"%s\%s" % (p, key))
+ self.macros["$(FrameworkVersion)"] = d["version"]
- # didn't find it; try existing path
- for p in string.split (os.environ['Path'],';'):
- fn = os.path.join(os.path.abspath(p),exe)
- if os.path.isfile(fn):
- return fn
+ def sub(self, s):
+ for k, v in self.macros.items():
+ s = string.replace(s, k, v)
+ return s
- return exe # last desperate hope
+def get_build_version():
+ """Return the version of MSVC that was used to build Python.
+ For Python 2.3 and up, the version number is included in
+ sys.version. For earlier versions, assume the compiler is MSVC 6.
+ """
-def set_path_env_var (name, version_number):
- """Set environment variable 'name' to an MSVC path type value obtained
- from 'get_msvc_paths()'. This is equivalent to a SET command prior
- to execution of spawned commands."""
-
- p = get_msvc_paths (name, version_number)
- if p:
- os.environ[name] = string.join (p,';')
-
+ prefix = "MSC v."
+ i = string.find(sys.version, prefix)
+ if i == -1:
+ return 6
+ i += len(prefix)
+ s, rest = sys.version[i:].split(" ", 1)
+ n = int(s[:-2])
+ if n == 12:
+ return 6
+ elif n == 13:
+ return 7
+ # else we don't know what version of the compiler this is
+ return None
+
class MSVCCompiler (CCompiler) :
"""Concrete class that implements an interface to Microsoft Visual C++,
@@ -199,39 +189,31 @@
static_lib_format = shared_lib_format = '%s%s'
exe_extension = '.exe'
-
- def __init__ (self,
- verbose=0,
- dry_run=0,
- force=0):
-
+ def __init__ (self, verbose=0, dry_run=0, force=0):
CCompiler.__init__ (self, verbose, dry_run, force)
- versions = get_devstudio_versions ()
-
- if versions:
- version = versions[0] # highest version
-
- self.cc = find_exe("cl.exe", version)
- self.linker = find_exe("link.exe", version)
- self.lib = find_exe("lib.exe", version)
- self.rc = find_exe("rc.exe", version) # resource compiler
- self.mc = find_exe("mc.exe", version) # message compiler
- set_path_env_var ('lib', version)
- set_path_env_var ('include', version)
- path=get_msvc_paths('path', version)
- try:
- for p in string.split(os.environ['path'],';'):
- path.append(p)
- except KeyError:
- pass
- os.environ['path'] = string.join(path,';')
+ self.__version = get_build_version()
+ if self.__version == 7:
+ self.__root = r"Software\Microsoft\VisualStudio"
+ self.__macros = MacroExpander(self.__version)
else:
- # devstudio not found in the registry
- self.cc = "cl.exe"
- self.linker = "link.exe"
- self.lib = "lib.exe"
- self.rc = "rc.exe"
- self.mc = "mc.exe"
+ self.__root = r"Software\Microsoft\Devstudio"
+ self.__paths = self.get_msvc_paths("path")
+
+ self.cc = self.find_exe("cl.exe")
+ self.linker = self.find_exe("link.exe")
+ self.lib = self.find_exe("lib.exe")
+ self.rc = self.find_exe("rc.exe") # resource compiler
+ self.mc = self.find_exe("mc.exe") # message compiler
+ self.set_path_env_var('lib')
+ self.set_path_env_var('include')
+
+ # extend the MSVC path with the current path
+ try:
+ for p in string.split(os.environ['path'], ';'):
+ self.__paths.append(p)
+ except KeyError:
+ pass
+ os.environ['path'] = string.join(self.__paths, ';')
self.preprocess_options = None
self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GX' ,
@@ -500,4 +482,68 @@
# find_library_file ()
-# class MSVCCompiler
+ # Helper methods for using the MSVC registry settings
+
+ def find_exe(self, exe):
+ """Return path to an MSVC executable program.
+
+ Tries to find the program in several places: first, one of the
+ MSVC program search paths from the registry; next, the directories
+ in the PATH environment variable. If any of those work, return an
+ absolute path that is known to exist. If none of them work, just
+ return the original program name, 'exe'.
+ """
+
+ for p in self.__paths:
+ fn = os.path.join(os.path.abspath(p), exe)
+ if os.path.isfile(fn):
+ return fn
+
+ # didn't find it; try existing path
+ for p in string.split(os.environ['Path'],';'):
+ fn = os.path.join(os.path.abspath(p),exe)
+ if os.path.isfile(fn):
+ return fn
+
+ return exe
+
+ def get_msvc_paths(self, path, platform='x86'):
+ """Get a list of devstudio directories (include, lib or path).
+
+ Return a list of strings. The list will be empty if unable to
+ access the registry or appropriate registry keys not found.
+ """
+
+ if not _can_read_reg:
+ return []
+
+ path = path + " dirs"
+ if self.__version == 7:
+ key = (r"%s\7.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"
+ % (self.__root,))
+ else:
+ key = (r"%s\6.0\Build System\Components\Platforms"
+
+ for base in HKEYS:
+ d = read_values(base, key)
+ if d:
+ if self.__version == 7:
+ return string.split(self.__macros.sub(d[path]), ";")
+ else:
+ return string.split(d[path], ";")
+ return []
+
+ def set_path_env_var(self, name):
+ """Set environment variable 'name' to an MSVC path type value.
+
+ This is equivalent to a SET command prior to execution of spawned
+ commands.
+ """
+
+ if name == "lib":
+ p = self.get_msvc_paths("library")
+ else:
+ p = self.get_msvc_paths(name)
+ if p:
+ os.environ[name] = string.join(p, ';')
+