]> granicus.if.org Git - python/commitdiff
Issue #23970: Adds distutils._msvccompiler for new Visual Studio versions.
authorSteve Dower <steve.dower@microsoft.com>
Sat, 23 May 2015 16:02:50 +0000 (09:02 -0700)
committerSteve Dower <steve.dower@microsoft.com>
Sat, 23 May 2015 16:02:50 +0000 (09:02 -0700)
Lib/distutils/_msvccompiler.py [new file with mode: 0644]
Lib/distutils/ccompiler.py
Lib/distutils/command/bdist_wininst.py
Lib/distutils/command/build_ext.py
Lib/distutils/tests/test_msvccompiler.py [new file with mode: 0644]
Lib/test/test_sundry.py

diff --git a/Lib/distutils/_msvccompiler.py b/Lib/distutils/_msvccompiler.py
new file mode 100644 (file)
index 0000000..896d9d9
--- /dev/null
@@ -0,0 +1,561 @@
+"""distutils._msvccompiler
+
+Contains MSVCCompiler, an implementation of the abstract CCompiler class
+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 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
+
+import winreg
+from itertools import count
+
+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
+
+        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
+
+        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:
+        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))
+
+    return {
+        key.lower(): value
+        for key, _, value in
+        (line.partition('=') for line in out.splitlines())
+        if key and value
+    }
+
+def _find_exe(exe, paths=None):
+    """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'.
+    """
+    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
+
+# 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++,
+       as defined by the CCompiler abstract class."""
+
+    compiler_type = 'msvc'
+
+    # Just set this so CCompiler's constructor doesn't barf.  We currently
+    # don't use the 'set_executables()' bureaucracy provided by CCompiler,
+    # as it really isn't necessary for this sort of single-compiler class.
+    # Would be nice to have a consistent interface with UnixCCompiler,
+    # though, so it's worth thinking about.
+    executables = {}
+
+    # Private class data (need to distinguish C from C++ source for compiler)
+    _c_extensions = ['.c']
+    _cpp_extensions = ['.cc', '.cpp', '.cxx']
+    _rc_extensions = ['.rc']
+    _mc_extensions = ['.mc']
+
+    # Needed for the filename generation methods provided by the
+    # base class, CCompiler.
+    src_extensions = (_c_extensions + _cpp_extensions +
+                      _rc_extensions + _mc_extensions)
+    res_extension = '.res'
+    obj_extension = '.obj'
+    static_lib_extension = '.lib'
+    shared_lib_extension = '.dll'
+    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)
+        # target platform (.plat_name is consistent with 'bdist')
+        self.plat_name = None
+        self.initialized = False
+
+    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:
+            # cross compile from win32 -> some 64bit
+            plat_spec = '{}_{}'.format(
+                PLAT_TO_VCVARS[get_platform()],
+                PLAT_TO_VCVARS[plat_name]
+            )
+
+        vc_env = _get_vc_env(plat_spec)
+        if not vc_env:
+            raise DistutilsPlatformError("Unable to find a compatible "
+                "Visual Studio installation.")
+
+        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
+
+        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
+        self.compile_options = [
+            '/nologo', '/Ox', '/MD', '/W3', '/GL', '/DNDEBUG'
+        ]
+        self.compile_options_debug = [
+            '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
+        ]
+
+        self.ldflags_shared = [
+            '/nologo', '/DLL', '/INCREMENTAL:NO'
+        ]
+        self.ldflags_shared_debug = [
+            '/nologo', '/DLL', '/INCREMENTAL:no', '/DEBUG:FULL'
+        ]
+        self.ldflags_static = [
+            '/nologo'
+        ]
+
+        self.initialized = True
+
+    # -- Worker methods ------------------------------------------------
+
+    def object_filenames(self,
+                         source_filenames,
+                         strip_dir=0,
+                         output_dir=''):
+        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 {}".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,
+                output_dir=None, macros=None, include_dirs=None, debug=0,
+                extra_preargs=None, extra_postargs=None, depends=None):
+
+        if not self.initialized:
+            self.initialize()
+        compile_info = self._setup_compile(output_dir, macros, include_dirs,
+                                           sources, depends, extra_postargs)
+        macros, objects, extra_postargs, pp_opts, build = compile_info
+
+        compile_opts = extra_preargs or []
+        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]
+            except KeyError:
+                continue
+            if debug:
+                # pass the full pathname to MSVC in debug mode,
+                # this allows the debugger to find the source file
+                # without asking the user to browse for it
+                src = os.path.abspath(src)
+
+            if ext in self._c_extensions:
+                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])
+                except DistutilsExecError as msg:
+                    raise CompileError(msg)
+                continue
+            elif ext in self._mc_extensions:
+                # Compile .MC to .RC file to .RES file.
+                #   * '-h dir' specifies the directory for the
+                #     generated include file
+                #   * '-r dir' specifies the target directory of the
+                #     generated RC file and the binary message resource
+                #     it includes
+                #
+                # For now (since there are no options to change this),
+                # we use the source-directory for the include file and
+                # the build directory for the RC file and message
+                # resources. This works at least for win32all.
+                h_dir = os.path.dirname(src)
+                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')
+                    # then compile .RC to .RES 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 {} to {}"
+                                   .format(src, 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(args)
+            except DistutilsExecError as msg:
+                raise CompileError(msg)
+
+        return objects
+
+
+    def create_static_lib(self,
+                          objects,
+                          output_libname,
+                          output_dir=None,
+                          debug=0,
+                          target_lang=None):
+
+        if not self.initialized:
+            self.initialize()
+        objects, output_dir = self._fix_object_args(objects, output_dir)
+        output_filename = self.library_filename(output_libname,
+                                                output_dir=output_dir)
+
+        if self._need_link(objects, output_filename):
+            lib_args = objects + ['/OUT:' + output_filename]
+            if debug:
+                pass # XXX what goes here?
+            try:
+                self.spawn([self.lib] + lib_args)
+            except DistutilsExecError as msg:
+                raise LibError(msg)
+        else:
+            log.debug("skipping %s (up-to-date)", output_filename)
+
+
+    def link(self,
+             target_desc,
+             objects,
+             output_filename,
+             output_dir=None,
+             libraries=None,
+             library_dirs=None,
+             runtime_library_dirs=None,
+             export_symbols=None,
+             debug=0,
+             extra_preargs=None,
+             extra_postargs=None,
+             build_temp=None,
+             target_lang=None):
+
+        if not self.initialized:
+            self.initialize()
+        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
+
+        if 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,
+                                   libraries)
+        if output_dir is not None:
+            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:
+                ldflags = ldflags[1:]
+
+            export_opts = []
+            for sym in (export_symbols or []):
+                export_opts.append("/EXPORT:" + sym)
+
+            ld_args = (ldflags + lib_opts + export_opts +
+                       objects + ['/OUT:' + output_filename])
+
+            # The MSVC linker generates .lib and .exp files, which cannot be
+            # suppressed by any linker switches. The .lib files may even be
+            # 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(
+                    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:
+                ld_args.extend(extra_postargs)
+
+            self.mkpath(os.path.dirname(output_filename))
+            try:
+                self.spawn([self.linker] + ld_args)
+            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
+    # ccompiler.py.
+
+    def library_dir_option(self, dir):
+        return "/LIBPATH:" + dir
+
+    def runtime_library_dir_option(self, dir):
+        raise DistutilsPlatformError(
+              "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.
+        if debug:
+            try_names = [lib + "_d", lib]
+        else:
+            try_names = [lib]
+        for dir in dirs:
+            for name in try_names:
+                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
index 911e84dd3b3f2ea8e848bd84602284be522e9744..b7df394eccc7f20872e6b713b6b878519998fbf8 100644 (file)
@@ -959,7 +959,7 @@ def get_default_compiler(osname=None, platform=None):
 # 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"),
index 959a8bf62ecedab63dc4ec5052ea1215f4bebcab..a3eff7e7cf9696efc9e69b13aa4062bfb887360a 100644 (file)
@@ -303,7 +303,6 @@ class bdist_wininst(Command):
         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 @@ class bdist_wininst(Command):
         # 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__)
index c5a3ce1915dbd8fd5ec2ce64723bb0a38a39222c..d4cb11e6dbd78ab3f95e1e6dcc7b9dd2e9a9d715 100644 (file)
@@ -19,10 +19,6 @@ from distutils import log
 
 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 @@ class build_ext(Command):
             _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 @@ class build_ext(Command):
         # 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_msvccompiler.py b/Lib/distutils/tests/test_msvccompiler.py
new file mode 100644 (file)
index 0000000..b440754
--- /dev/null
@@ -0,0 +1,160 @@
+"""Tests for distutils._msvccompiler."""
+import sys
+import unittest
+import os
+
+from distutils.errors import DistutilsPlatformError
+from distutils.tests import support
+from test.support import run_unittest
+
+# A manifest with the only assembly reference being the msvcrt assembly, so
+# should have the assembly completely stripped.  Note that although the
+# assembly has a <security> reference the assembly is removed - that is
+# currently a "feature", not a bug :)
+_MANIFEST_WITH_ONLY_MSVC_REFERENCE = """\
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+          manifestVersion="1.0">
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level="asInvoker" uiAccess="false">
+        </requestedExecutionLevel>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT"
+         version="9.0.21022.8" processorArchitecture="x86"
+         publicKeyToken="XXXX">
+      </assemblyIdentity>
+    </dependentAssembly>
+  </dependency>
+</assembly>
+"""
+
+# A manifest with references to assemblies other than msvcrt.  When processed,
+# this assembly should be returned with just the msvcrt part removed.
+_MANIFEST_WITH_MULTIPLE_REFERENCES = """\
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+          manifestVersion="1.0">
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level="asInvoker" uiAccess="false">
+        </requestedExecutionLevel>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT"
+         version="9.0.21022.8" processorArchitecture="x86"
+         publicKeyToken="XXXX">
+      </assemblyIdentity>
+    </dependentAssembly>
+  </dependency>
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity type="win32" name="Microsoft.VC90.MFC"
+        version="9.0.21022.8" processorArchitecture="x86"
+        publicKeyToken="XXXX"></assemblyIdentity>
+    </dependentAssembly>
+  </dependency>
+</assembly>
+"""
+
+_CLEANED_MANIFEST = """\
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+          manifestVersion="1.0">
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel level="asInvoker" uiAccess="false">
+        </requestedExecutionLevel>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+  <dependency>
+
+  </dependency>
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity type="win32" name="Microsoft.VC90.MFC"
+        version="9.0.21022.8" processorArchitecture="x86"
+        publicKeyToken="XXXX"></assemblyIdentity>
+    </dependentAssembly>
+  </dependency>
+</assembly>"""
+
+SKIP_MESSAGE = (None if sys.platform == "win32" else
+                "These tests are only for win32")
+
+@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE)
+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._msvccompiler import _get_vc_env
+        def _find_vcvarsall():
+            return None
+
+        import distutils._msvccompiler as _msvccompiler
+        old_find_vcvarsall = _msvccompiler._find_vcvarsall
+        _msvccompiler._find_vcvarsall = _find_vcvarsall
+        try:
+            self.assertRaises(DistutilsPlatformError, _get_vc_env,
+                             'wont find this version')
+        finally:
+            _msvccompiler._find_vcvarsall = old_find_vcvarsall
+
+    def test_remove_visual_c_ref(self):
+        from distutils._msvccompiler import MSVCCompiler
+        tempdir = self.mkdtemp()
+        manifest = os.path.join(tempdir, 'manifest')
+        f = open(manifest, 'w')
+        try:
+            f.write(_MANIFEST_WITH_MULTIPLE_REFERENCES)
+        finally:
+            f.close()
+
+        compiler = MSVCCompiler()
+        compiler._remove_visual_c_ref(manifest)
+
+        # see what we got
+        f = open(manifest)
+        try:
+            # removing trailing spaces
+            content = '\n'.join([line.rstrip() for line in f.readlines()])
+        finally:
+            f.close()
+
+        # makes sure the manifest was properly cleaned
+        self.assertEqual(content, _CLEANED_MANIFEST)
+
+    def test_remove_entire_manifest(self):
+        from distutils._msvccompiler import MSVCCompiler
+        tempdir = self.mkdtemp()
+        manifest = os.path.join(tempdir, 'manifest')
+        f = open(manifest, 'w')
+        try:
+            f.write(_MANIFEST_WITH_ONLY_MSVC_REFERENCE)
+        finally:
+            f.close()
+
+        compiler = MSVCCompiler()
+        got = compiler._remove_visual_c_ref(manifest)
+        self.assertIsNone(got)
+
+
+def test_suite():
+    return unittest.makeSuite(msvccompilerTestCase)
+
+if __name__ == "__main__":
+    run_unittest(test_suite())
index e99ca9ed37f8ff6480145d93ab090b7348eaefaa..1fb996469821b359c75f24f599b7660e1b1e516d 100644 (file)
@@ -22,8 +22,6 @@ class TestUntestedModules(unittest.TestCase):
             import distutils.ccompiler
             import distutils.cygwinccompiler
             import distutils.filelist
-            if sys.platform.startswith('win'):
-                import distutils.msvccompiler
             import distutils.text_file
             import distutils.unixccompiler