Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/c-analyzer/distutils/msvc9compiler.py
12 views
1
"""distutils.msvc9compiler
2
3
Contains MSVCCompiler, an implementation of the abstract CCompiler class
4
for the Microsoft Visual Studio 2008.
5
6
The module is compatible with VS 2005 and VS 2008. You can find legacy support
7
for 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
15
import os
16
import subprocess
17
import sys
18
import re
19
20
from distutils.errors import DistutilsPlatformError
21
from distutils.ccompiler import CCompiler
22
from distutils import log
23
24
import winreg
25
26
RegOpenKeyEx = winreg.OpenKeyEx
27
RegEnumKey = winreg.EnumKey
28
RegEnumValue = winreg.EnumValue
29
RegError = winreg.error
30
31
HKEYS = (winreg.HKEY_USERS,
32
winreg.HKEY_CURRENT_USER,
33
winreg.HKEY_LOCAL_MACHINE,
34
winreg.HKEY_CLASSES_ROOT)
35
36
NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32)
37
if 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"
44
else:
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.)
52
PLAT_TO_VCVARS = {
53
'win32' : 'x86',
54
'win-amd64' : 'amd64',
55
}
56
57
class 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
119
class 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;
142
extensions must be built with a compiler than can generate compatible binaries.
143
Visual Studio 2008 was not found on this system. If you have Cygwin installed,
144
you 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
165
def 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
190
def 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
204
def 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
215
def 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
250
def 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
291
VERSION = get_build_version()
292
if VERSION < 8.0:
293
raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)
294
# MACROS = MacroExpander(VERSION)
295
296
class 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
440