• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""distutils.msvc9compiler
2
3Contains MSVCCompiler, an implementation of the abstract CCompiler class
4for the Microsoft Visual Studio 2008.
5
6The module is compatible with VS 2005 and VS 2008. You can find legacy support
7for older versions of VS in distutils.msvccompiler.
8"""
9
10# Written by Perry Stoll
11# hacked by Robin Becker and Thomas Heller to do a better job of
12#   finding DevStudio (through the registry)
13# ported to VS2005 and VS 2008 by Christian Heimes
14
15import os
16import subprocess
17import sys
18import re
19
20from distutils.errors import DistutilsPlatformError
21from distutils.ccompiler import CCompiler
22from distutils import log
23
24import winreg
25
26RegOpenKeyEx = winreg.OpenKeyEx
27RegEnumKey = winreg.EnumKey
28RegEnumValue = winreg.EnumValue
29RegError = winreg.error
30
31HKEYS = (winreg.HKEY_USERS,
32         winreg.HKEY_CURRENT_USER,
33         winreg.HKEY_LOCAL_MACHINE,
34         winreg.HKEY_CLASSES_ROOT)
35
36NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32)
37if NATIVE_WIN64:
38    # Visual C++ is a 32-bit application, so we need to look in
39    # the corresponding registry branch, if we're running a
40    # 64-bit Python on Win64
41    VS_BASE = r"Software\Wow6432Node\Microsoft\VisualStudio\%0.1f"
42    WINSDK_BASE = r"Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows"
43    NET_BASE = r"Software\Wow6432Node\Microsoft\.NETFramework"
44else:
45    VS_BASE = r"Software\Microsoft\VisualStudio\%0.1f"
46    WINSDK_BASE = r"Software\Microsoft\Microsoft SDKs\Windows"
47    NET_BASE = r"Software\Microsoft\.NETFramework"
48
49# A map keyed by get_platform() return values to values accepted by
50# 'vcvarsall.bat'.  Note a cross-compile may combine these (eg, 'x86_amd64' is
51# the param to cross-compile on x86 targeting amd64.)
52PLAT_TO_VCVARS = {
53    'win32' : 'x86',
54    'win-amd64' : 'amd64',
55}
56
57class Reg:
58    """Helper class to read values from the registry
59    """
60
61    def get_value(cls, path, key):
62        for base in HKEYS:
63            d = cls.read_values(base, path)
64            if d and key in d:
65                return d[key]
66        raise KeyError(key)
67    get_value = classmethod(get_value)
68
69    def read_keys(cls, base, key):
70        """Return list of registry keys."""
71        try:
72            handle = RegOpenKeyEx(base, key)
73        except RegError:
74            return None
75        L = []
76        i = 0
77        while True:
78            try:
79                k = RegEnumKey(handle, i)
80            except RegError:
81                break
82            L.append(k)
83            i += 1
84        return L
85    read_keys = classmethod(read_keys)
86
87    def read_values(cls, base, key):
88        """Return dict of registry keys and values.
89
90        All names are converted to lowercase.
91        """
92        try:
93            handle = RegOpenKeyEx(base, key)
94        except RegError:
95            return None
96        d = {}
97        i = 0
98        while True:
99            try:
100                name, value, type = RegEnumValue(handle, i)
101            except RegError:
102                break
103            name = name.lower()
104            d[cls.convert_mbcs(name)] = cls.convert_mbcs(value)
105            i += 1
106        return d
107    read_values = classmethod(read_values)
108
109    def convert_mbcs(s):
110        dec = getattr(s, "decode", None)
111        if dec is not None:
112            try:
113                s = dec("mbcs")
114            except UnicodeError:
115                pass
116        return s
117    convert_mbcs = staticmethod(convert_mbcs)
118
119class MacroExpander:
120
121    def __init__(self, version):
122        self.macros = {}
123        self.vsbase = VS_BASE % version
124        self.load_macros(version)
125
126    def set_macro(self, macro, path, key):
127        self.macros["$(%s)" % macro] = Reg.get_value(path, key)
128
129    def load_macros(self, version):
130        self.set_macro("VCInstallDir", self.vsbase + r"\Setup\VC", "productdir")
131        self.set_macro("VSInstallDir", self.vsbase + r"\Setup\VS", "productdir")
132        self.set_macro("FrameworkDir", NET_BASE, "installroot")
133        try:
134            if version >= 8.0:
135                self.set_macro("FrameworkSDKDir", NET_BASE,
136                               "sdkinstallrootv2.0")
137            else:
138                raise KeyError("sdkinstallrootv2.0")
139        except KeyError:
140            raise DistutilsPlatformError(
141            """Python was built with Visual Studio 2008;
142extensions must be built with a compiler than can generate compatible binaries.
143Visual Studio 2008 was not found on this system. If you have Cygwin installed,
144you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
145
146        if version >= 9.0:
147            self.set_macro("FrameworkVersion", self.vsbase, "clr version")
148            self.set_macro("WindowsSdkDir", WINSDK_BASE, "currentinstallfolder")
149        else:
150            p = r"Software\Microsoft\NET Framework Setup\Product"
151            for base in HKEYS:
152                try:
153                    h = RegOpenKeyEx(base, p)
154                except RegError:
155                    continue
156                key = RegEnumKey(h, 0)
157                d = Reg.get_value(base, r"%s\%s" % (p, key))
158                self.macros["$(FrameworkVersion)"] = d["version"]
159
160    def sub(self, s):
161        for k, v in self.macros.items():
162            s = s.replace(k, v)
163        return s
164
165def get_build_version():
166    """Return the version of MSVC that was used to build Python.
167
168    For Python 2.3 and up, the version number is included in
169    sys.version.  For earlier versions, assume the compiler is MSVC 6.
170    """
171    prefix = "MSC v."
172    i = sys.version.find(prefix)
173    if i == -1:
174        return 6
175    i = i + len(prefix)
176    s, rest = sys.version[i:].split(" ", 1)
177    majorVersion = int(s[:-2]) - 6
178    if majorVersion >= 13:
179        # v13 was skipped and should be v14
180        majorVersion += 1
181    minorVersion = int(s[2:3]) / 10.0
182    # I don't think paths are affected by minor version in version 6
183    if majorVersion == 6:
184        minorVersion = 0
185    if majorVersion >= 6:
186        return majorVersion + minorVersion
187    # else we don't know what version of the compiler this is
188    return None
189
190def normalize_and_reduce_paths(paths):
191    """Return a list of normalized paths with duplicates removed.
192
193    The current order of paths is maintained.
194    """
195    # Paths are normalized so things like:  /a and /a/ aren't both preserved.
196    reduced_paths = []
197    for p in paths:
198        np = os.path.normpath(p)
199        # XXX(nnorwitz): O(n**2), if reduced_paths gets long perhaps use a set.
200        if np not in reduced_paths:
201            reduced_paths.append(np)
202    return reduced_paths
203
204def removeDuplicates(variable):
205    """Remove duplicate values of an environment variable.
206    """
207    oldList = variable.split(os.pathsep)
208    newList = []
209    for i in oldList:
210        if i not in newList:
211            newList.append(i)
212    newVariable = os.pathsep.join(newList)
213    return newVariable
214
215def find_vcvarsall(version):
216    """Find the vcvarsall.bat file
217
218    At first it tries to find the productdir of VS 2008 in the registry. If
219    that fails it falls back to the VS90COMNTOOLS env var.
220    """
221    vsbase = VS_BASE % version
222    try:
223        productdir = Reg.get_value(r"%s\Setup\VC" % vsbase,
224                                   "productdir")
225    except KeyError:
226        log.debug("Unable to find productdir in registry")
227        productdir = None
228
229    if not productdir or not os.path.isdir(productdir):
230        toolskey = "VS%0.f0COMNTOOLS" % version
231        toolsdir = os.environ.get(toolskey, None)
232
233        if toolsdir and os.path.isdir(toolsdir):
234            productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC")
235            productdir = os.path.abspath(productdir)
236            if not os.path.isdir(productdir):
237                log.debug("%s is not a valid directory" % productdir)
238                return None
239        else:
240            log.debug("Env var %s is not set or invalid" % toolskey)
241    if not productdir:
242        log.debug("No productdir found")
243        return None
244    vcvarsall = os.path.join(productdir, "vcvarsall.bat")
245    if os.path.isfile(vcvarsall):
246        return vcvarsall
247    log.debug("Unable to find vcvarsall.bat")
248    return None
249
250def query_vcvarsall(version, arch="x86"):
251    """Launch vcvarsall.bat and read the settings from its environment
252    """
253    vcvarsall = find_vcvarsall(version)
254    interesting = {"include", "lib", "libpath", "path"}
255    result = {}
256
257    if vcvarsall is None:
258        raise DistutilsPlatformError("Unable to find vcvarsall.bat")
259    log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version)
260    popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch),
261                             stdout=subprocess.PIPE,
262                             stderr=subprocess.PIPE)
263    try:
264        stdout, stderr = popen.communicate()
265        if popen.wait() != 0:
266            raise DistutilsPlatformError(stderr.decode("mbcs"))
267
268        stdout = stdout.decode("mbcs")
269        for line in stdout.split("\n"):
270            line = Reg.convert_mbcs(line)
271            if '=' not in line:
272                continue
273            line = line.strip()
274            key, value = line.split('=', 1)
275            key = key.lower()
276            if key in interesting:
277                if value.endswith(os.pathsep):
278                    value = value[:-1]
279                result[key] = removeDuplicates(value)
280
281    finally:
282        popen.stdout.close()
283        popen.stderr.close()
284
285    if len(result) != len(interesting):
286        raise ValueError(str(list(result.keys())))
287
288    return result
289
290# More globals
291VERSION = get_build_version()
292if VERSION < 8.0:
293    raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)
294# MACROS = MacroExpander(VERSION)
295
296class MSVCCompiler(CCompiler) :
297    """Concrete class that implements an interface to Microsoft Visual C++,
298       as defined by the CCompiler abstract class."""
299
300    compiler_type = 'msvc'
301
302    # Just set this so CCompiler's constructor doesn't barf.  We currently
303    # don't use the 'set_executables()' bureaucracy provided by CCompiler,
304    # as it really isn't necessary for this sort of single-compiler class.
305    # Would be nice to have a consistent interface with UnixCCompiler,
306    # though, so it's worth thinking about.
307    executables = {}
308
309    # Private class data (need to distinguish C from C++ source for compiler)
310    _c_extensions = ['.c']
311    _cpp_extensions = ['.cc', '.cpp', '.cxx']
312    _rc_extensions = ['.rc']
313    _mc_extensions = ['.mc']
314
315    # Needed for the filename generation methods provided by the
316    # base class, CCompiler.
317    src_extensions = (_c_extensions + _cpp_extensions +
318                      _rc_extensions + _mc_extensions)
319    res_extension = '.res'
320    obj_extension = '.obj'
321    static_lib_extension = '.lib'
322    shared_lib_extension = '.dll'
323    static_lib_format = shared_lib_format = '%s%s'
324    exe_extension = '.exe'
325
326    def __init__(self, verbose=0, dry_run=0, force=0):
327        CCompiler.__init__ (self, verbose, dry_run, force)
328        self.__version = VERSION
329        self.__root = r"Software\Microsoft\VisualStudio"
330        # self.__macros = MACROS
331        self.__paths = []
332        # target platform (.plat_name is consistent with 'bdist')
333        self.plat_name = None
334        self.__arch = None # deprecated name
335        self.initialized = False
336
337    # -- Worker methods ------------------------------------------------
338
339    def manifest_setup_ldargs(self, output_filename, build_temp, ld_args):
340        # If we need a manifest at all, an embedded manifest is recommended.
341        # See MSDN article titled
342        # "How to: Embed a Manifest Inside a C/C++ Application"
343        # (currently at http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx)
344        # Ask the linker to generate the manifest in the temp dir, so
345        # we can check it, and possibly embed it, later.
346        temp_manifest = os.path.join(
347                build_temp,
348                os.path.basename(output_filename) + ".manifest")
349        ld_args.append('/MANIFESTFILE:' + temp_manifest)
350
351    def manifest_get_embed_info(self, target_desc, ld_args):
352        # If a manifest should be embedded, return a tuple of
353        # (manifest_filename, resource_id).  Returns None if no manifest
354        # should be embedded.  See http://bugs.python.org/issue7833 for why
355        # we want to avoid any manifest for extension modules if we can.
356        for arg in ld_args:
357            if arg.startswith("/MANIFESTFILE:"):
358                temp_manifest = arg.split(":", 1)[1]
359                break
360        else:
361            # no /MANIFESTFILE so nothing to do.
362            return None
363        if target_desc == CCompiler.EXECUTABLE:
364            # by default, executables always get the manifest with the
365            # CRT referenced.
366            mfid = 1
367        else:
368            # Extension modules try and avoid any manifest if possible.
369            mfid = 2
370            temp_manifest = self._remove_visual_c_ref(temp_manifest)
371        if temp_manifest is None:
372            return None
373        return temp_manifest, mfid
374
375    def _remove_visual_c_ref(self, manifest_file):
376        try:
377            # Remove references to the Visual C runtime, so they will
378            # fall through to the Visual C dependency of Python.exe.
379            # This way, when installed for a restricted user (e.g.
380            # runtimes are not in WinSxS folder, but in Python's own
381            # folder), the runtimes do not need to be in every folder
382            # with .pyd's.
383            # Returns either the filename of the modified manifest or
384            # None if no manifest should be embedded.
385            manifest_f = open(manifest_file)
386            try:
387                manifest_buf = manifest_f.read()
388            finally:
389                manifest_f.close()
390            pattern = re.compile(
391                r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
392                r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
393                re.DOTALL)
394            manifest_buf = re.sub(pattern, "", manifest_buf)
395            pattern = r"<dependentAssembly>\s*</dependentAssembly>"
396            manifest_buf = re.sub(pattern, "", manifest_buf)
397            # Now see if any other assemblies are referenced - if not, we
398            # don't want a manifest embedded.
399            pattern = re.compile(
400                r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""
401                r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)
402            if re.search(pattern, manifest_buf) is None:
403                return None
404
405            manifest_f = open(manifest_file, 'w')
406            try:
407                manifest_f.write(manifest_buf)
408                return manifest_file
409            finally:
410                manifest_f.close()
411        except OSError:
412            pass
413
414    # -- Miscellaneous methods -----------------------------------------
415
416    # Helper methods for using the MSVC registry settings
417
418    def find_exe(self, exe):
419        """Return path to an MSVC executable program.
420
421        Tries to find the program in several places: first, one of the
422        MSVC program search paths from the registry; next, the directories
423        in the PATH environment variable.  If any of those work, return an
424        absolute path that is known to exist.  If none of them work, just
425        return the original program name, 'exe'.
426        """
427        for p in self.__paths:
428            fn = os.path.join(os.path.abspath(p), exe)
429            if os.path.isfile(fn):
430                return fn
431
432        # didn't find it; try existing path
433        for p in os.environ['Path'].split(';'):
434            fn = os.path.join(os.path.abspath(p),exe)
435            if os.path.isfile(fn):
436                return fn
437
438        return exe
439