summaryrefslogtreecommitdiff
path: root/setuptools/msvc9_support.py
blob: 85a8cf4497b28611c843d5642421501f8fe71af4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
import os
import itertools
import distutils.errors

try:
    import distutils.msvc9compiler
except ImportError:
    pass

import six


unpatched = dict()

def patch_for_specialized_compiler():
    """
    Patch functions in distutils.msvc9compiler to use the standalone compiler
    build for Python (Windows only). Fall back to original behavior when the
    standalone compiler is not available.
    """
    if 'distutils' not in globals():
        # The module isn't available to be patched
        return

    if unpatched:
        # Already patched
        return

    unpatched.update(vars(distutils.msvc9compiler))

    distutils.msvc9compiler.find_vcvarsall = find_vcvarsall
    distutils.msvc9compiler.query_vcvarsall = query_vcvarsall

def find_vcvarsall(version):
    Reg = distutils.msvc9compiler.Reg
    VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
    key = VC_BASE % ('', version)
    try:
        # Per-user installs register the compiler path here
        productdir = Reg.get_value(key, "installdir")
    except KeyError:
        try:
            # All-user installs on a 64-bit system register here
            key = VC_BASE % ('Wow6432Node\\', version)
            productdir = Reg.get_value(key, "installdir")
        except KeyError:
            productdir = None

    if productdir:
        vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
        if os.path.isfile(vcvarsall):
            return vcvarsall

    return unpatched['find_vcvarsall'](version)

def query_vcvarsall(version, arch='x86', *args, **kwargs):
    message = ''

    # Try to get environement from vcvarsall.bat (Classical way)
    try:
        return unpatched['query_vcvarsall'](version, arch, *args, **kwargs)
    except distutils.errors.DistutilsPlatformError as exc:
        # Error if Vcvarsall.bat is missing
        message = exc.args[0]
    except ValueError as exc:
        # Error if environment not set after executing vcvarsall.bat
        message = exc.args[0]

    # If vcvarsall.bat fail, try to set environment directly
    try:
        return _query_vcvarsall(version, arch)
    except distutils.errors.DistutilsPlatformError as exc:
        # Error if MSVC++ directory not found or environment not set
        message = exc.args[0]

    # Raise error
    if message and "vcvarsall.bat" in message:
        # Special error message if MSVC++ not installed
        message = 'Microsoft Visual C++ %0.1f is required (%s).' %\
            (version, message)
        if int(version) == 9:
            # For VC++ 9.0 Redirect user to Vc++ for Python 2.7 :
            # This redirection link is maintained by Microsoft.
            # Contact vspython@microsoft.com if it needs updating.
            message += r' Get it from http://aka.ms/vcpython27'
        elif int(version) == 10:
            # For VC++ 10.0 Redirect user to Windows SDK 7.1
            message += ' Get it with "Microsoft Windows SDK for Windows 7": '
            message += r'www.microsoft.com/download/details.aspx?id=8279'

    raise distutils.errors.DistutilsPlatformError(message)


class PlatformInfo:
    current_cpu = os.environ['processor_architecture'].lower()
    win_dir = os.environ['WinDir']
    program_files = os.environ['ProgramFiles']
    program_files_x86 = os.environ.get('ProgramFiles(x86)', program_files)

    def __init__(self, arch):
        self.arch = arch

    @property
    def target_cpu(self):
        return self.arch[self.arch.find('_') + 1:]

    def target_is_x86(self):
        return self.target_cpu == 'x86'

    def current_is_x86(self):
        return self.current_cpu != 'x86'

    @property
    def lib_extra(self):
        return (
            r'\amd64' if self.target_cpu == 'amd64' else
            r'\ia64' if self.target_cpu == 'ia64' else
            ''
        )

    @property
    def sdk_extra(self):
        return (
            r'\x64' if self.target_cpu == 'amd64' else
            r'\ia64' if self.target_cpu == 'ia64' else
            ''
        )

    @property
    def tools_extra(self):
        path = self.lib_extra
        if self.target_cpu != self.current_cpu:
            path = path.replace('\\', '\\x86_')
        return path


class RegistryInfo:
    def __init__(self, platform_info, version):
        self.platform_info = platform_info
        self.version = version

    @property
    def microsoft(self):
        return os.path.join(
            'Software',
            '' if self.platform_info.current_is_x86() else 'Wow6432Node',
            'Microsoft',
        )

    @property
    def sxs(self):
        return os.path.join(self.microsoft, r'VisualStudio\SxS')

    @property
    def vc(self):
        return os.path.join(self.sxs, 'VC7')

    @property
    def vs(self):
        return os.path.join(self.sxs, 'VS7')

    @property
    def vc_for_python(self):
        path = r'DevDiv\VCForPython\%0.1f' % self.version
        return os.path.join(self.microsoft, path)

    @property
    def windows_sdk(self):
        return os.path.join(self.microsoft, r'Microsoft SDKs\Windows')

    def find_visual_studio(self):
        """
        Find Microsoft Visual Studio directory
        """
        name = 'Microsoft Visual Studio %0.1f' % self.version
        default = os.path.join(self.platform_info.program_files_x86, name)
        return self.lookup(self.vs, '%0.1f' % self.version) or default

    def find_visual_c(self):
        """
        Find Microsoft Visual C++ directory
        """
        # If fail, use default path
        default = r'Microsoft Visual Studio %0.1f\VC' % self.version
        guess_vc = os.path.join(self.platform_info.program_files_x86, default)

        # Try to get "VC++ for Python" version from registry
        install_base = self.lookup(self.vc_for_python, 'installdir')
        default_vc = os.path.join(install_base, 'VC') if install_base else guess_vc

        result = self.lookup(self.vc, '%0.1f' % self.version) or default_vc

        if not os.path.isdir(result):
            msg = 'vcvarsall.bat and Visual C++ directory not found'
            raise distutils.errors.DistutilsPlatformError(msg)

        return result

    def find_windows_sdk(self):
        """
        Find Microsoft Windows SDK directory
        """
        WindowsSdkDir = ''
        if self.version == 9.0:
            WindowsSdkVer = ('7.0', '6.1', '6.0a')
        elif self.version == 10.0:
            WindowsSdkVer = ('7.1', '7.0a')
        else:
            WindowsSdkVer = ()
        for ver in WindowsSdkVer:
            # Try to get it from registry
            loc = os.path.join(self.windows_sdk, 'v%s' % ver)
            WindowsSdkDir = self.lookup(loc, 'installationfolder')
            if WindowsSdkDir:
                break
        if not WindowsSdkDir or not os.path.isdir(WindowsSdkDir):
            # Try to get "VC++ for Python" version from registry
            install_base = self.lookup(self.vc_for_python, 'installdir')
            if install_base:
                WindowsSdkDir = os.path.join(install_base, 'WinSDK')
        if not WindowsSdkDir or not os.path.isdir(WindowsSdkDir):
            # If fail, use default path
            for ver in WindowsSdkVer:
                path = r'Microsoft SDKs\Windows\v%s' % ver
                d = os.path.join(self.platform_info.program_files, path)
                if os.path.isdir(d):
                    WindowsSdkDir = d
        if not WindowsSdkDir:
            # If fail, use Platform SDK
            WindowsSdkDir = os.path.join(self.find_visual_c(), 'PlatformSDK')
        return WindowsSdkDir

    def find_dot_net_versions(self):
        """
        Find Microsoft .NET Framework Versions
        """
        if self.version == 10.0:
            v4 = self.lookup(self.vc, 'frameworkver32') or ''
            if v4.lower()[:2] != 'v4':
                v4 = None
            # default to last v4 version
            v4 = v4 or 'v4.0.30319'
            FrameworkVer = (v4, 'v3.5')
        elif self.version == 9.0:
            FrameworkVer = ('v3.5', 'v2.0.50727')
        elif self.version == 8.0:
            FrameworkVer = ('v3.0', 'v2.0.50727')
        return FrameworkVer

    def lookup(self, base, key):
        try:
            return distutils.msvc9compiler.Reg.get_value(base, key)
        except KeyError:
            pass


def _query_vcvarsall(version, arch):
    """
    Return environment variables for specified Microsoft Visual C++ version
    and platform.
    """
    pi = PlatformInfo(arch)
    reg = RegistryInfo(pi, version)

    # Find Microsoft .NET Framework 32bit directory
    guess_fw = os.path.join(pi.win_dir, r'Microsoft.NET\Framework')
    FrameworkDir32 = reg.lookup(reg.vc, 'frameworkdir32') or guess_fw

    # Find Microsoft .NET Framework 64bit directory
    guess_fw64 = os.path.join(pi.win_dir, r'Microsoft.NET\Framework64')
    FrameworkDir64 = reg.lookup(reg.vc, 'frameworkdir64') or guess_fw64

    # Set Microsoft Visual Studio Tools
    VSTools = [
        os.path.join(reg.find_visual_studio(), r'Common7\IDE'),
        os.path.join(reg.find_visual_studio(), r'Common7\Tools'),
    ]

    # Set Microsoft Visual C++ Includes
    VCIncludes = [os.path.join(reg.find_visual_c(), 'Include')]

    # Set Microsoft Visual C++ & Microsoft Foundation Class Libraries
    VCLibraries = [
        os.path.join(reg.find_visual_c(), 'Lib' + pi.lib_extra),
        os.path.join(reg.find_visual_c(), r'ATLMFC\LIB' + pi.lib_extra),
    ]

    # Set Microsoft Visual C++ Tools
    VCTools = [
        os.path.join(reg.find_visual_c(), 'VCPackages'),
        os.path.join(reg.find_visual_c(), 'Bin' + pi.tools_extra),
    ]
    if pi.tools_extra:
        VCTools.append(os.path.join(reg.find_visual_c(), 'Bin'))

    # Set Microsoft Windows SDK Include
    OSLibraries = [os.path.join(reg.find_windows_sdk(), 'Lib' + pi.sdk_extra)]

    # Set Microsoft Windows SDK Libraries
    OSIncludes = [
        os.path.join(reg.find_windows_sdk(), 'Include'),
        os.path.join(reg.find_windows_sdk(), r'Include\gl'),
    ]

    # Set Microsoft Windows SDK Tools
    SdkTools = [os.path.join(reg.find_windows_sdk(), 'Bin')]
    if not pi.target_is_x86():
        SdkTools.append(os.path.join(reg.find_windows_sdk(), 'Bin' + pi.sdk_extra))
    if version == 10.0:
        path = r'Bin\NETFX 4.0 Tools' + pi.sdk_extra
        SdkTools.append(os.path.join(reg.find_windows_sdk(), path))

    # Set Microsoft Windows SDK Setup
    SdkSetup = [os.path.join(reg.find_windows_sdk(), 'Setup')]

    # Set Microsoft .NET Framework Tools
    roots = [FrameworkDir32]
    include_64_framework = not pi.target_is_x86() and not pi.current_is_x86()
    roots += [FrameworkDir64] if include_64_framework else []

    FxTools = [
        os.path.join(root, ver)
        for root, ver in itertools.product(roots, reg.find_dot_net_versions())
    ]

    # Set Microsoft Visual Studio Team System Database
    VsTDb = [os.path.join(reg.find_visual_studio(), r'VSTSDB\Deploy')]

    return dict(
        include=_build_paths('include', [VCIncludes, OSIncludes]),
        lib=_build_paths('lib', [VCLibraries, OSLibraries, FxTools]),
        libpath=_build_paths('libpath', [VCLibraries, FxTools]),
        path=_build_paths('path', [VCTools, VSTools, VsTDb, SdkTools, SdkSetup, FxTools]),
    )


def _build_paths(name, spec_path_lists):
    """
    Given an environment variable name and specified paths,
    return a pathsep-separated string of paths containing
    unique, extant, directories from those paths and from
    the environment variable. Raise an error if no paths
    are resolved.
    """
    # flatten spec_path_lists
    spec_paths = itertools.chain.from_iterable(spec_path_lists)
    env_paths = os.environ.get(name, '').split(os.pathsep)
    paths = itertools.chain(spec_paths, env_paths)
    extant_paths = list(filter(os.path.isdir, paths))
    if not extant_paths:
        msg = "%s environment variable is empty" % name.upper()
        raise distutils.errors.DistutilsPlatformError(msg)
    unique_paths = unique_everseen(extant_paths)
    return os.pathsep.join(unique_paths)


# from Python docs
def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    filterfalse = six.moves.filterfalse
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element