[Python-checkins] cpython: Issue #23970: Adds distutils._msvccompiler for new Visual Studio versions.

steve.dower python-checkins at python.org
Sat May 23 18:04:47 CEST 2015


https://hg.python.org/cpython/rev/b2ee6206fa5e
changeset:   96243:b2ee6206fa5e
parent:      96238:a811f5561c99
user:        Steve Dower <steve.dower at microsoft.com>
date:        Sat May 23 09:02:50 2015 -0700
summary:
  Issue #23970: Adds distutils._msvccompiler for new Visual Studio versions.

files:
  Lib/distutils/msvccompiler.py             |  686 ++++-----
  Lib/distutils/ccompiler.py                |    2 +-
  Lib/distutils/command/bdist_wininst.py    |   31 +-
  Lib/distutils/command/build_ext.py        |   34 +-
  Lib/distutils/tests/test_msvc9compiler.py |   52 +-
  Lib/test/test_sundry.py                   |    2 -
  6 files changed, 346 insertions(+), 461 deletions(-)


diff --git a/Lib/distutils/msvccompiler.py b/Lib/distutils/_msvccompiler.py
copy from Lib/distutils/msvccompiler.py
copy to Lib/distutils/_msvccompiler.py
--- a/Lib/distutils/msvccompiler.py
+++ b/Lib/distutils/_msvccompiler.py
@@ -1,201 +1,120 @@
-"""distutils.msvccompiler
+"""distutils._msvccompiler
 
 Contains MSVCCompiler, an implementation of the abstract CCompiler class
-for the Microsoft Visual Studio.
+for Microsoft Visual Studio 2015.
+
+The module is compatible with VS 2015 and later. You can find legacy support
+for older versions in distutils.msvc9compiler and distutils.msvccompiler.
 """
 
 # Written by Perry Stoll
 # hacked by Robin Becker and Thomas Heller to do a better job of
 #   finding DevStudio (through the registry)
+# ported to VS 2005 and VS 2008 by Christian Heimes
+# ported to VS 2015 by Steve Dower
 
-import sys, os
-from distutils.errors import \
-     DistutilsExecError, DistutilsPlatformError, \
-     CompileError, LibError, LinkError
-from distutils.ccompiler import \
-     CCompiler, gen_preprocess_options, gen_lib_options
+import os
+import subprocess
+import re
+
+from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
+                             CompileError, LibError, LinkError
+from distutils.ccompiler import CCompiler, gen_lib_options
 from distutils import log
+from distutils.util import get_platform
 
-_can_read_reg = False
-try:
-    import winreg
+import winreg
+from itertools import count
 
-    _can_read_reg = True
-    hkey_mod = winreg
+def _find_vcvarsall():
+    with winreg.OpenKeyEx(
+        winreg.HKEY_LOCAL_MACHINE,
+        r"Software\Microsoft\VisualStudio\SxS\VC7",
+        access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+    ) as key:
+        if not key:
+            log.debug("Visual C++ is not registered")
+            return None
 
-    RegOpenKeyEx = winreg.OpenKeyEx
-    RegEnumKey = winreg.EnumKey
-    RegEnumValue = winreg.EnumValue
-    RegError = winreg.error
+        best_version = 0
+        best_dir = None
+        for i in count():
+            try:
+                v, vc_dir, vt = winreg.EnumValue(key, i)
+            except OSError:
+                break
+            if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
+                try:
+                    version = int(float(v))
+                except (ValueError, TypeError):
+                    continue
+                if version >= 14 and version > best_version:
+                    best_version, best_dir = version, vc_dir
+        if not best_version:
+            log.debug("No suitable Visual C++ version found")
+            return None
 
-except ImportError:
+        vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
+        if not os.path.isfile(vcvarsall):
+            log.debug("%s cannot be found", vcvarsall)
+            return None
+
+        return vcvarsall
+
+def _get_vc_env(plat_spec):
+    if os.getenv("DISTUTILS_USE_SDK"):
+        return {
+            key.lower(): value
+            for key, value in os.environ.items()
+        }
+
+    vcvarsall = _find_vcvarsall()
+    if not vcvarsall:
+        raise DistutilsPlatformError("Unable to find vcvarsall.bat")
+
     try:
-        import win32api
-        import win32con
-        _can_read_reg = True
-        hkey_mod = win32con
+        out = subprocess.check_output(
+            '"{}" {} && set'.format(vcvarsall, plat_spec),
+            shell=True,
+            stderr=subprocess.STDOUT,
+            universal_newlines=True,
+        )
+    except subprocess.CalledProcessError as exc:
+        log.error(exc.output)
+        raise DistutilsPlatformError("Error executing {}"
+                .format(exc.cmd))
 
-        RegOpenKeyEx = win32api.RegOpenKeyEx
-        RegEnumKey = win32api.RegEnumKey
-        RegEnumValue = win32api.RegEnumValue
-        RegError = win32api.error
-    except ImportError:
-        log.info("Warning: Can't read registry to find the "
-                 "necessary compiler setting\n"
-                 "Make sure that Python modules winreg, "
-                 "win32api or win32con are installed.")
-        pass
+    return {
+        key.lower(): value
+        for key, _, value in
+        (line.partition('=') for line in out.splitlines())
+        if key and value
+    }
 
-if _can_read_reg:
-    HKEYS = (hkey_mod.HKEY_USERS,
-             hkey_mod.HKEY_CURRENT_USER,
-             hkey_mod.HKEY_LOCAL_MACHINE,
-             hkey_mod.HKEY_CLASSES_ROOT)
+def _find_exe(exe, paths=None):
+    """Return path to an MSVC executable program.
 
-def read_keys(base, key):
-    """Return list of registry keys."""
-    try:
-        handle = RegOpenKeyEx(base, key)
-    except RegError:
-        return None
-    L = []
-    i = 0
-    while True:
-        try:
-            k = RegEnumKey(handle, i)
-        except RegError:
-            break
-        L.append(k)
-        i += 1
-    return L
+    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'.
+    """
+    if not paths:
+        paths = os.getenv('path').split(os.pathsep)
+    for p in paths:
+        fn = os.path.join(os.path.abspath(p), exe)
+        if os.path.isfile(fn):
+            return fn
+    return exe
 
-def read_values(base, key):
-    """Return dict of registry keys and values.
-
-    All names are converted to lowercase.
-    """
-    try:
-        handle = RegOpenKeyEx(base, key)
-    except RegError:
-        return None
-    d = {}
-    i = 0
-    while True:
-        try:
-            name, value, type = RegEnumValue(handle, i)
-        except RegError:
-            break
-        name = name.lower()
-        d[convert_mbcs(name)] = convert_mbcs(value)
-        i += 1
-    return d
-
-def convert_mbcs(s):
-    dec = getattr(s, "decode", None)
-    if dec is not None:
-        try:
-            s = dec("mbcs")
-        except UnicodeError:
-            pass
-    return s
-
-class MacroExpander:
-    def __init__(self, version):
-        self.macros = {}
-        self.load_macros(version)
-
-    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\%0.1f" % 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")
-        try:
-            if version > 7.0:
-                self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")
-            else:
-                self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
-        except KeyError as exc: #
-            raise DistutilsPlatformError(
-            """Python was built with Visual Studio 2003;
-extensions must be built with a compiler than can generate compatible binaries.
-Visual Studio 2003 was not found on this system. If you have Cygwin installed,
-you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
-
-        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"]
-
-    def sub(self, s):
-        for k, v in self.macros.items():
-            s = s.replace(k, v)
-        return s
-
-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.
-    """
-    prefix = "MSC v."
-    i = sys.version.find(prefix)
-    if i == -1:
-        return 6
-    i = i + len(prefix)
-    s, rest = sys.version[i:].split(" ", 1)
-    majorVersion = int(s[:-2]) - 6
-    if majorVersion >= 13:
-        # v13 was skipped and should be v14
-        majorVersion += 1
-    minorVersion = int(s[2:3]) / 10.0
-    # I don't think paths are affected by minor version in version 6
-    if majorVersion == 6:
-        minorVersion = 0
-    if majorVersion >= 6:
-        return majorVersion + minorVersion
-    # else we don't know what version of the compiler this is
-    return None
-
-def get_build_architecture():
-    """Return the processor architecture.
-
-    Possible results are "Intel", "Itanium", or "AMD64".
-    """
-
-    prefix = " bit ("
-    i = sys.version.find(prefix)
-    if i == -1:
-        return "Intel"
-    j = sys.version.find(")", i)
-    return sys.version[i+len(prefix):j]
-
-def normalize_and_reduce_paths(paths):
-    """Return a list of normalized paths with duplicates removed.
-
-    The current order of paths is maintained.
-    """
-    # Paths are normalized so things like:  /a and /a/ aren't both preserved.
-    reduced_paths = []
-    for p in paths:
-        np = os.path.normpath(p)
-        # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.
-        if np not in reduced_paths:
-            reduced_paths.append(np)
-    return reduced_paths
-
+# A map keyed by get_platform() return values to values accepted by
+# 'vcvarsall.bat'.  Note a cross-compile may combine these (eg, 'x86_amd64' is
+# the param to cross-compile on x86 targetting amd64.)
+PLAT_TO_VCVARS = {
+    'win32' : 'x86',
+    'win-amd64' : 'amd64',
+}
 
 class MSVCCompiler(CCompiler) :
     """Concrete class that implements an interface to Microsoft Visual C++,
@@ -227,83 +146,75 @@
     static_lib_format = shared_lib_format = '%s%s'
     exe_extension = '.exe'
 
+
     def __init__(self, verbose=0, dry_run=0, force=0):
         CCompiler.__init__ (self, verbose, dry_run, force)
-        self.__version = get_build_version()
-        self.__arch = get_build_architecture()
-        if self.__arch == "Intel":
-            # x86
-            if self.__version >= 7:
-                self.__root = r"Software\Microsoft\VisualStudio"
-                self.__macros = MacroExpander(self.__version)
-            else:
-                self.__root = r"Software\Microsoft\Devstudio"
-            self.__product = "Visual Studio version %s" % self.__version
-        else:
-            # Win64. Assume this was built with the platform SDK
-            self.__product = "Microsoft SDK compiler %s" % (self.__version + 6)
-
+        # target platform (.plat_name is consistent with 'bdist')
+        self.plat_name = None
         self.initialized = False
 
-    def initialize(self):
-        self.__paths = []
-        if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"):
-            # Assume that the SDK set up everything alright; don't try to be
-            # smarter
-            self.cc = "cl.exe"
-            self.linker = "link.exe"
-            self.lib = "lib.exe"
-            self.rc = "rc.exe"
-            self.mc = "mc.exe"
+    def initialize(self, plat_name=None):
+        # multi-init means we would need to check platform same each time...
+        assert not self.initialized, "don't init multiple times"
+        if plat_name is None:
+            plat_name = get_platform()
+        # sanity check for platforms to prevent obscure errors later.
+        if plat_name not in PLAT_TO_VCVARS:
+            raise DistutilsPlatformError("--plat-name must be one of {}"
+                                         .format(tuple(PLAT_TO_VCVARS)))
+
+        # On x86, 'vcvarsall.bat amd64' creates an env that doesn't work;
+        # to cross compile, you use 'x86_amd64'.
+        # On AMD64, 'vcvarsall.bat amd64' is a native build env; to cross
+        # compile use 'x86' (ie, it runs the x86 compiler directly)
+        if plat_name == get_platform() or plat_name == 'win32':
+            # native build or cross-compile to win32
+            plat_spec = PLAT_TO_VCVARS[plat_name]
         else:
-            self.__paths = self.get_msvc_paths("path")
+            # cross compile from win32 -> some 64bit
+            plat_spec = '{}_{}'.format(
+                PLAT_TO_VCVARS[get_platform()],
+                PLAT_TO_VCVARS[plat_name]
+            )
 
-            if len(self.__paths) == 0:
-                raise DistutilsPlatformError("Python was built with %s, "
-                       "and extensions need to be built with the same "
-                       "version of the compiler, but it isn't installed."
-                       % self.__product)
+        vc_env = _get_vc_env(plat_spec)
+        if not vc_env:
+            raise DistutilsPlatformError("Unable to find a compatible "
+                "Visual Studio installation.")
 
-            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')
+        paths = vc_env.get('path', '').split(os.pathsep)
+        self.cc = _find_exe("cl.exe", paths)
+        self.linker = _find_exe("link.exe", paths)
+        self.lib = _find_exe("lib.exe", paths)
+        self.rc = _find_exe("rc.exe", paths)   # resource compiler
+        self.mc = _find_exe("mc.exe", paths)   # message compiler
+        self.mt = _find_exe("mt.exe", paths)   # message compiler
 
-        # extend the MSVC path with the current path
-        try:
-            for p in os.environ['path'].split(';'):
-                self.__paths.append(p)
-        except KeyError:
-            pass
-        self.__paths = normalize_and_reduce_paths(self.__paths)
-        os.environ['path'] = ";".join(self.__paths)
+        for dir in vc_env.get('include', '').split(os.pathsep):
+            if dir:
+                self.add_include_dir(dir)
+
+        for dir in vc_env.get('lib', '').split(os.pathsep):
+            if dir:
+                self.add_library_dir(dir)
 
         self.preprocess_options = None
-        if self.__arch == "Intel":
-            self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GX' ,
-                                     '/DNDEBUG']
-            self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX',
-                                          '/Z7', '/D_DEBUG']
-        else:
-            # Win64
-            self.compile_options = [ '/nologo', '/Ox', '/MD', '/W3', '/GS-' ,
-                                     '/DNDEBUG']
-            self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-',
-                                          '/Z7', '/D_DEBUG']
+        self.compile_options = [
+            '/nologo', '/Ox', '/MD', '/W3', '/GL', '/DNDEBUG'
+        ]
+        self.compile_options_debug = [
+            '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
+        ]
 
-        self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
-        if self.__version >= 7:
-            self.ldflags_shared_debug = [
-                '/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG'
-                ]
-        else:
-            self.ldflags_shared_debug = [
-                '/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG'
-                ]
-        self.ldflags_static = [ '/nologo']
+        self.ldflags_shared = [
+            '/nologo', '/DLL', '/INCREMENTAL:NO'
+        ]
+        self.ldflags_shared_debug = [
+            '/nologo', '/DLL', '/INCREMENTAL:no', '/DEBUG:FULL'
+        ]
+        self.ldflags_static = [
+            '/nologo'
+        ]
 
         self.initialized = True
 
@@ -313,31 +224,31 @@
                          source_filenames,
                          strip_dir=0,
                          output_dir=''):
-        # Copied from ccompiler.py, extended to return .res as 'object'-file
-        # for .rc input file
-        if output_dir is None: output_dir = ''
-        obj_names = []
-        for src_name in source_filenames:
-            (base, ext) = os.path.splitext (src_name)
-            base = os.path.splitdrive(base)[1] # Chop off the drive
-            base = base[os.path.isabs(base):]  # If abs, chop off leading /
-            if ext not in self.src_extensions:
+        ext_map = {ext: self.obj_extension for ext in self.src_extensions}
+        ext_map.update((ext, self.res_extension)
+                for ext in self._rc_extensions + self._mc_extensions)
+
+        def make_out_path(p):
+            base, ext = os.path.splitext(p)
+            if strip_dir:
+                base = os.path.basename(base)
+            else:
+                _, base = os.path.splitdrive(base)
+                if base.startswith((os.path.sep, os.path.altsep)):
+                    base = base[1:]
+            try:
+                return base + ext_map[ext]
+            except LookupError:
                 # Better to raise an exception instead of silently continuing
                 # and later complain about sources and targets having
                 # different lengths
-                raise CompileError ("Don't know how to compile %s" % src_name)
-            if strip_dir:
-                base = os.path.basename (base)
-            if ext in self._rc_extensions:
-                obj_names.append (os.path.join (output_dir,
-                                                base + self.res_extension))
-            elif ext in self._mc_extensions:
-                obj_names.append (os.path.join (output_dir,
-                                                base + self.res_extension))
-            else:
-                obj_names.append (os.path.join (output_dir,
-                                                base + self.obj_extension))
-        return obj_names
+                raise CompileError("Don't know how to compile {}".format(p))
+
+        output_dir = output_dir or ''
+        return [
+            os.path.join(output_dir, make_out_path(src_name))
+            for src_name in source_filenames
+        ]
 
 
     def compile(self, sources,
@@ -351,12 +262,15 @@
         macros, objects, extra_postargs, pp_opts, build = compile_info
 
         compile_opts = extra_preargs or []
-        compile_opts.append ('/c')
+        compile_opts.append('/c')
         if debug:
             compile_opts.extend(self.compile_options_debug)
         else:
             compile_opts.extend(self.compile_options)
 
+
+        add_cpp_opts = False
+
         for obj in objects:
             try:
                 src, ext = build[obj]
@@ -372,13 +286,13 @@
                 input_opt = "/Tc" + src
             elif ext in self._cpp_extensions:
                 input_opt = "/Tp" + src
+                add_cpp_opts = True
             elif ext in self._rc_extensions:
                 # compile .RC to .RES file
                 input_opt = src
                 output_opt = "/fo" + obj
                 try:
-                    self.spawn([self.rc] + pp_opts +
-                               [output_opt] + [input_opt])
+                    self.spawn([self.rc] + pp_opts + [output_opt, input_opt])
                 except DistutilsExecError as msg:
                     raise CompileError(msg)
                 continue
@@ -398,27 +312,29 @@
                 rc_dir = os.path.dirname(obj)
                 try:
                     # first compile .MC to .RC and .H file
-                    self.spawn([self.mc] +
-                               ['-h', h_dir, '-r', rc_dir] + [src])
-                    base, _ = os.path.splitext (os.path.basename (src))
-                    rc_file = os.path.join (rc_dir, base + '.rc')
+                    self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
+                    base, _ = os.path.splitext(os.path.basename (src))
+                    rc_file = os.path.join(rc_dir, base + '.rc')
                     # then compile .RC to .RES file
-                    self.spawn([self.rc] +
-                               ["/fo" + obj] + [rc_file])
+                    self.spawn([self.rc, "/fo" + obj, rc_file])
 
                 except DistutilsExecError as msg:
                     raise CompileError(msg)
                 continue
             else:
                 # how to handle this file?
-                raise CompileError("Don't know how to compile %s to %s"
-                                   % (src, obj))
+                raise CompileError("Don't know how to compile {} to {}"
+                                   .format(src, obj))
 
-            output_opt = "/Fo" + obj
+            args = [self.cc] + compile_opts + pp_opts
+            if add_cpp_opts:
+                args.append('/EHsc')
+            args.append(input_opt)
+            args.append("/Fo" + obj)
+            args.extend(extra_postargs)
+
             try:
-                self.spawn([self.cc] + compile_opts + pp_opts +
-                           [input_opt, output_opt] +
-                           extra_postargs)
+                self.spawn(args)
             except DistutilsExecError as msg:
                 raise CompileError(msg)
 
@@ -434,7 +350,7 @@
 
         if not self.initialized:
             self.initialize()
-        (objects, output_dir) = self._fix_object_args(objects, output_dir)
+        objects, output_dir = self._fix_object_args(objects, output_dir)
         output_filename = self.library_filename(output_libname,
                                                 output_dir=output_dir)
 
@@ -467,14 +383,14 @@
 
         if not self.initialized:
             self.initialize()
-        (objects, output_dir) = self._fix_object_args(objects, output_dir)
+        objects, output_dir = self._fix_object_args(objects, output_dir)
         fixed_args = self._fix_lib_args(libraries, library_dirs,
                                         runtime_library_dirs)
-        (libraries, library_dirs, runtime_library_dirs) = fixed_args
+        libraries, library_dirs, runtime_library_dirs = fixed_args
 
         if runtime_library_dirs:
-            self.warn ("I don't know what to do with 'runtime_library_dirs': "
-                       + str (runtime_library_dirs))
+            self.warn("I don't know what to do with 'runtime_library_dirs': "
+                       + str(runtime_library_dirs))
 
         lib_opts = gen_lib_options(self,
                                    library_dirs, runtime_library_dirs,
@@ -483,16 +399,10 @@
             output_filename = os.path.join(output_dir, output_filename)
 
         if self._need_link(objects, output_filename):
+            ldflags = (self.ldflags_shared_debug if debug
+                       else self.ldflags_shared)
             if target_desc == CCompiler.EXECUTABLE:
-                if debug:
-                    ldflags = self.ldflags_shared_debug[1:]
-                else:
-                    ldflags = self.ldflags_shared[1:]
-            else:
-                if debug:
-                    ldflags = self.ldflags_shared_debug
-                else:
-                    ldflags = self.ldflags_shared
+                ldflags = ldflags[1:]
 
             export_opts = []
             for sym in (export_symbols or []):
@@ -506,14 +416,17 @@
             # needed! Make sure they are generated in the temporary build
             # directory. Since they have different names for debug and release
             # builds, they can go into the same directory.
+            build_temp = os.path.dirname(objects[0])
             if export_symbols is not None:
                 (dll_name, dll_ext) = os.path.splitext(
                     os.path.basename(output_filename))
                 implib_file = os.path.join(
-                    os.path.dirname(objects[0]),
+                    build_temp,
                     self.library_filename(dll_name))
                 ld_args.append ('/IMPLIB:' + implib_file)
 
+            self.manifest_setup_ldargs(output_filename, build_temp, ld_args)
+
             if extra_preargs:
                 ld_args[:0] = extra_preargs
             if extra_postargs:
@@ -525,9 +438,97 @@
             except DistutilsExecError as msg:
                 raise LinkError(msg)
 
+            # embed the manifest
+            # XXX - this is somewhat fragile - if mt.exe fails, distutils
+            # will still consider the DLL up-to-date, but it will not have a
+            # manifest.  Maybe we should link to a temp file?  OTOH, that
+            # implies a build environment error that shouldn't go undetected.
+            mfinfo = self.manifest_get_embed_info(target_desc, ld_args)
+            if mfinfo is not None:
+                mffilename, mfid = mfinfo
+                out_arg = '-outputresource:{};{}'.format(output_filename, mfid)
+                try:
+                    self.spawn([self.mt, '-nologo', '-manifest',
+                                mffilename, out_arg])
+                except DistutilsExecError as msg:
+                    raise LinkError(msg)
         else:
             log.debug("skipping %s (up-to-date)", output_filename)
 
+    def manifest_setup_ldargs(self, output_filename, build_temp, ld_args):
+        # If we need a manifest at all, an embedded manifest is recommended.
+        # See MSDN article titled
+        # "How to: Embed a Manifest Inside a C/C++ Application"
+        # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)
+        # Ask the linker to generate the manifest in the temp dir, so
+        # we can check it, and possibly embed it, later.
+        temp_manifest = os.path.join(
+                build_temp,
+                os.path.basename(output_filename) + ".manifest")
+        ld_args.append('/MANIFESTFILE:' + temp_manifest)
+
+    def manifest_get_embed_info(self, target_desc, ld_args):
+        # If a manifest should be embedded, return a tuple of
+        # (manifest_filename, resource_id).  Returns None if no manifest
+        # should be embedded.  See http://bugs.python.org/issue7833 for why
+        # we want to avoid any manifest for extension modules if we can)
+        for arg in ld_args:
+            if arg.startswith("/MANIFESTFILE:"):
+                temp_manifest = arg.split(":", 1)[1]
+                break
+        else:
+            # no /MANIFESTFILE so nothing to do.
+            return None
+        if target_desc == CCompiler.EXECUTABLE:
+            # by default, executables always get the manifest with the
+            # CRT referenced.
+            mfid = 1
+        else:
+            # Extension modules try and avoid any manifest if possible.
+            mfid = 2
+            temp_manifest = self._remove_visual_c_ref(temp_manifest)
+        if temp_manifest is None:
+            return None
+        return temp_manifest, mfid
+
+    def _remove_visual_c_ref(self, manifest_file):
+        try:
+            # Remove references to the Visual C runtime, so they will
+            # fall through to the Visual C dependency of Python.exe.
+            # This way, when installed for a restricted user (e.g.
+            # runtimes are not in WinSxS folder, but in Python's own
+            # folder), the runtimes do not need to be in every folder
+            # with .pyd's.
+            # Returns either the filename of the modified manifest or
+            # None if no manifest should be embedded.
+            manifest_f = open(manifest_file)
+            try:
+                manifest_buf = manifest_f.read()
+            finally:
+                manifest_f.close()
+            pattern = re.compile(
+                r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
+                r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
+                re.DOTALL)
+            manifest_buf = re.sub(pattern, "", manifest_buf)
+            pattern = "<dependentAssembly>\s*</dependentAssembly>"
+            manifest_buf = re.sub(pattern, "", manifest_buf)
+            # Now see if any other assemblies are referenced - if not, we
+            # don't want a manifest embedded.
+            pattern = re.compile(
+                r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""
+                r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)
+            if re.search(pattern, manifest_buf) is None:
+                return None
+
+            manifest_f = open(manifest_file, 'w')
+            try:
+                manifest_f.write(manifest_buf)
+                return manifest_file
+            finally:
+                manifest_f.close()
+        except OSError:
+            pass
 
     # -- Miscellaneous methods -----------------------------------------
     # These are all used by the 'gen_lib_options() function, in
@@ -538,12 +539,11 @@
 
     def runtime_library_dir_option(self, dir):
         raise DistutilsPlatformError(
-              "don't know how to set runtime library search path for MSVC++")
+              "don't know how to set runtime library search path for MSVC")
 
     def library_option(self, lib):
         return self.library_filename(lib)
 
-
     def find_library_file(self, dirs, lib, debug=0):
         # Prefer a debugging library if found (and requested), but deal
         # with it if we don't have one.
@@ -553,91 +553,9 @@
             try_names = [lib]
         for dir in dirs:
             for name in try_names:
-                libfile = os.path.join(dir, self.library_filename (name))
-                if os.path.exists(libfile):
+                libfile = os.path.join(dir, self.library_filename(name))
+                if os.path.isfile(libfile):
                     return libfile
         else:
             # Oops, didn't find it in *any* of 'dirs'
             return None
-
-    # 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 os.environ['Path'].split(';'):
-            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\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"
-                   % (self.__root, self.__version))
-        else:
-            key = (r"%s\6.0\Build System\Components\Platforms"
-                   r"\Win32 (%s)\Directories" % (self.__root, platform))
-
-        for base in HKEYS:
-            d = read_values(base, key)
-            if d:
-                if self.__version >= 7:
-                    return self.__macros.sub(d[path]).split(";")
-                else:
-                    return d[path].split(";")
-        # MSVC 6 seems to create the registry entries we need only when
-        # the GUI is run.
-        if self.__version == 6:
-            for base in HKEYS:
-                if read_values(base, r"%s\6.0" % self.__root) is not None:
-                    self.warn("It seems you have Visual Studio 6 installed, "
-                        "but the expected registry settings are not present.\n"
-                        "You must at least run the Visual Studio GUI once "
-                        "so that these entries are created.")
-                    break
-        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] = ';'.join(p)
-
-
-if get_build_version() >= 8.0:
-    log.debug("Importing new compiler from distutils.msvc9compiler")
-    OldMSVCCompiler = MSVCCompiler
-    from distutils.msvc9compiler import MSVCCompiler
-    # get_build_architecture not really relevant now we support cross-compile
-    from distutils.msvc9compiler import MacroExpander
diff --git a/Lib/distutils/ccompiler.py b/Lib/distutils/ccompiler.py
--- a/Lib/distutils/ccompiler.py
+++ b/Lib/distutils/ccompiler.py
@@ -959,7 +959,7 @@
 # is assumed to be in the 'distutils' package.)
 compiler_class = { 'unix':    ('unixccompiler', 'UnixCCompiler',
                                "standard UNIX-style compiler"),
-                   'msvc':    ('msvccompiler', 'MSVCCompiler',
+                   'msvc':    ('_msvccompiler', 'MSVCCompiler',
                                "Microsoft Visual C++"),
                    'cygwin':  ('cygwinccompiler', 'CygwinCCompiler',
                                "Cygwin port of GNU C Compiler for Win32"),
diff --git a/Lib/distutils/command/bdist_wininst.py b/Lib/distutils/command/bdist_wininst.py
--- a/Lib/distutils/command/bdist_wininst.py
+++ b/Lib/distutils/command/bdist_wininst.py
@@ -303,7 +303,6 @@
         return installer_name
 
     def get_exe_bytes(self):
-        from distutils.msvccompiler import get_build_version
         # If a target-version other than the current version has been
         # specified, then using the MSVC version from *this* build is no good.
         # Without actually finding and executing the target version and parsing
@@ -313,20 +312,28 @@
         # We can then execute this program to obtain any info we need, such
         # as the real sys.version string for the build.
         cur_version = get_python_version()
-        if self.target_version and self.target_version != cur_version:
-            # If the target version is *later* than us, then we assume they
-            # use what we use
-            # string compares seem wrong, but are what sysconfig.py itself uses
-            if self.target_version > cur_version:
-                bv = get_build_version()
+
+        # If the target version is *later* than us, then we assume they
+        # use what we use
+        # string compares seem wrong, but are what sysconfig.py itself uses
+        if self.target_version and self.target_version < cur_version:
+            if self.target_version < "2.4":
+                bv = 6.0
+            elif self.target_version == "2.4":
+                bv = 7.1
+            elif self.target_version == "2.5":
+                bv = 8.0
+            elif self.target_version <= "3.2":
+                bv = 9.0
+            elif self.target_version <= "3.4":
+                bv = 10.0
             else:
-                if self.target_version < "2.4":
-                    bv = 6.0
-                else:
-                    bv = 7.1
+                bv = 14.0
         else:
             # for current version - use authoritative check.
-            bv = get_build_version()
+            from msvcrt import CRT_ASSEMBLY_VERSION
+            bv = float('.'.join(CRT_ASSEMBLY_VERSION.split('.', 2)[:2]))
+
 
         # wininst-x.y.exe is in the same directory as this file
         directory = os.path.dirname(__file__)
diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py
--- a/Lib/distutils/command/build_ext.py
+++ b/Lib/distutils/command/build_ext.py
@@ -19,10 +19,6 @@
 
 from site import USER_BASE
 
-if os.name == 'nt':
-    from distutils.msvccompiler import get_build_version
-    MSVC_VERSION = int(get_build_version())
-
 # An extension name is just a dot-separated list of Python NAMEs (ie.
 # the same as a fully-qualified module name).
 extension_name_re = re.compile \
@@ -206,27 +202,17 @@
             _sys_home = getattr(sys, '_home', None)
             if _sys_home:
                 self.library_dirs.append(_sys_home)
-            if MSVC_VERSION >= 9:
-                # Use the .lib files for the correct architecture
-                if self.plat_name == 'win32':
-                    suffix = 'win32'
-                else:
-                    # win-amd64 or win-ia64
-                    suffix = self.plat_name[4:]
-                new_lib = os.path.join(sys.exec_prefix, 'PCbuild')
-                if suffix:
-                    new_lib = os.path.join(new_lib, suffix)
-                self.library_dirs.append(new_lib)
 
-            elif MSVC_VERSION == 8:
-                self.library_dirs.append(os.path.join(sys.exec_prefix,
-                                         'PC', 'VS8.0'))
-            elif MSVC_VERSION == 7:
-                self.library_dirs.append(os.path.join(sys.exec_prefix,
-                                         'PC', 'VS7.1'))
+            # Use the .lib files for the correct architecture
+            if self.plat_name == 'win32':
+                suffix = 'win32'
             else:
-                self.library_dirs.append(os.path.join(sys.exec_prefix,
-                                         'PC', 'VC6'))
+                # win-amd64 or win-ia64
+                suffix = self.plat_name[4:]
+            new_lib = os.path.join(sys.exec_prefix, 'PCbuild')
+            if suffix:
+                new_lib = os.path.join(new_lib, suffix)
+            self.library_dirs.append(new_lib)
 
         # for extensions under Cygwin and AtheOS Python's library directory must be
         # appended to library_dirs
@@ -716,7 +702,7 @@
         # to need it mentioned explicitly, though, so that's what we do.
         # Append '_d' to the python import library on debug builds.
         if sys.platform == "win32":
-            from distutils.msvccompiler import MSVCCompiler
+            from distutils._msvccompiler import MSVCCompiler
             if not isinstance(self.compiler, MSVCCompiler):
                 template = "python%d%d"
                 if self.debug:
diff --git a/Lib/distutils/tests/test_msvc9compiler.py b/Lib/distutils/tests/test_msvccompiler.py
copy from Lib/distutils/tests/test_msvc9compiler.py
copy to Lib/distutils/tests/test_msvccompiler.py
--- a/Lib/distutils/tests/test_msvc9compiler.py
+++ b/Lib/distutils/tests/test_msvccompiler.py
@@ -1,4 +1,4 @@
-"""Tests for distutils.msvc9compiler."""
+"""Tests for distutils._msvccompiler."""
 import sys
 import unittest
 import os
@@ -90,56 +90,32 @@
   </dependency>
 </assembly>"""
 
-if sys.platform=="win32":
-    from distutils.msvccompiler import get_build_version
-    if get_build_version()>=8.0:
-        SKIP_MESSAGE = None
-    else:
-        SKIP_MESSAGE = "These tests are only for MSVC8.0 or above"
-else:
-    SKIP_MESSAGE = "These tests are only for win32"
+SKIP_MESSAGE = (None if sys.platform == "win32" else
+                "These tests are only for win32")
 
 @unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE)
-class msvc9compilerTestCase(support.TempdirManager,
+class msvccompilerTestCase(support.TempdirManager,
                             unittest.TestCase):
 
     def test_no_compiler(self):
         # makes sure query_vcvarsall raises
         # a DistutilsPlatformError if the compiler
         # is not found
-        from distutils.msvc9compiler import query_vcvarsall
-        def _find_vcvarsall(version):
+        from distutils._msvccompiler import _get_vc_env
+        def _find_vcvarsall():
             return None
 
-        from distutils import msvc9compiler
-        old_find_vcvarsall = msvc9compiler.find_vcvarsall
-        msvc9compiler.find_vcvarsall = _find_vcvarsall
+        import distutils._msvccompiler as _msvccompiler
+        old_find_vcvarsall = _msvccompiler._find_vcvarsall
+        _msvccompiler._find_vcvarsall = _find_vcvarsall
         try:
-            self.assertRaises(DistutilsPlatformError, query_vcvarsall,
+            self.assertRaises(DistutilsPlatformError, _get_vc_env,
                              'wont find this version')
         finally:
-            msvc9compiler.find_vcvarsall = old_find_vcvarsall
-
-    def test_reg_class(self):
-        from distutils.msvc9compiler import Reg
-        self.assertRaises(KeyError, Reg.get_value, 'xxx', 'xxx')
-
-        # looking for values that should exist on all
-        # windows registeries versions.
-        path = r'Control Panel\Desktop'
-        v = Reg.get_value(path, 'dragfullwindows')
-        self.assertIn(v, ('0', '1', '2'))
-
-        import winreg
-        HKCU = winreg.HKEY_CURRENT_USER
-        keys = Reg.read_keys(HKCU, 'xxxx')
-        self.assertEqual(keys, None)
-
-        keys = Reg.read_keys(HKCU, r'Control Panel')
-        self.assertIn('Desktop', keys)
+            _msvccompiler._find_vcvarsall = old_find_vcvarsall
 
     def test_remove_visual_c_ref(self):
-        from distutils.msvc9compiler import MSVCCompiler
+        from distutils._msvccompiler import MSVCCompiler
         tempdir = self.mkdtemp()
         manifest = os.path.join(tempdir, 'manifest')
         f = open(manifest, 'w')
@@ -163,7 +139,7 @@
         self.assertEqual(content, _CLEANED_MANIFEST)
 
     def test_remove_entire_manifest(self):
-        from distutils.msvc9compiler import MSVCCompiler
+        from distutils._msvccompiler import MSVCCompiler
         tempdir = self.mkdtemp()
         manifest = os.path.join(tempdir, 'manifest')
         f = open(manifest, 'w')
@@ -178,7 +154,7 @@
 
 
 def test_suite():
-    return unittest.makeSuite(msvc9compilerTestCase)
+    return unittest.makeSuite(msvccompilerTestCase)
 
 if __name__ == "__main__":
     run_unittest(test_suite())
diff --git a/Lib/test/test_sundry.py b/Lib/test/test_sundry.py
--- a/Lib/test/test_sundry.py
+++ b/Lib/test/test_sundry.py
@@ -22,8 +22,6 @@
             import distutils.ccompiler
             import distutils.cygwinccompiler
             import distutils.filelist
-            if sys.platform.startswith('win'):
-                import distutils.msvccompiler
             import distutils.text_file
             import distutils.unixccompiler
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list