-
Notifications
You must be signed in to change notification settings - Fork 946
/
compiler_id.py
237 lines (213 loc) · 8.38 KB
/
compiler_id.py
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
import os
import tempfile
from six import StringIO
from conans.client.runner import ConanRunner
from conans.model.version import Version
GCC = "gcc"
LLVM_GCC = "llvm-gcc" # GCC frontend with LLVM backend
CLANG = "clang"
APPLE_CLANG = "apple-clang"
SUNCC = "suncc"
MSVC = "Visual Studio"
INTEL = "intel"
QCC = "qcc"
MCST_LCC = "mcst-lcc"
class CompilerId(object):
"""
compiler identification description, holds compiler name, full version and
other important properties
"""
def __init__(self, name, major, minor, patch):
self._name = name
self._major = major
self._minor = minor
self._patch = patch
self._version = Version(self.version)
@property
def name(self):
return self._name
@property
def major(self):
return self._major
@property
def minor(self):
return self._minor
@property
def patch(self):
return self._patch
@property
def version(self):
return "%s.%s.%s" % (self._major, self._minor, self._patch)
@property
def major_minor(self):
return "%s.%s" % (self._major, self._minor)
def __str__(self):
return "%s %s" % (self._name, self.version)
def __repr__(self):
return self.__str__()
def __eq__(self, other):
return self.name == other.name and self._version == other._version
def __ne__(self, other):
return not self.__eq__(other)
UNKNOWN_COMPILER = CompilerId(None, None, None, None)
# convert _MSC_VER to the corresponding Visual Studio version
MSVC_TO_VS_VERSION = {800: (1, 0),
900: (2, 0),
1000: (4, 0),
1010: (4, 1),
1020: (4, 2),
1100: (5, 0),
1200: (6, 0),
1300: (7, 0),
1310: (7, 1),
1400: (8, 0),
1500: (9, 0),
1600: (10, 0),
1700: (11, 0),
1800: (12, 0),
1900: (14, 0),
1910: (15, 0),
1911: (15, 3),
1912: (15, 5),
1913: (15, 6),
1914: (15, 7),
1915: (15, 8),
1916: (15, 9),
1920: (16, 0),
1921: (16, 1),
1922: (16, 2),
1923: (16, 3),
1924: (16, 4),
1925: (16, 5),
1926: (16, 6),
1927: (16, 7),
1928: (16, 8),
1929: (16, 10),
1930: (17, 0)}
def _parse_compiler_version(defines):
try:
if '__LCC__' in defines and '__e2k__' in defines:
compiler = MCST_LCC
version = int(defines['__LCC__'])
major = int(version / 100)
minor = int(version % 100)
patch = int(defines['__LCC_MINOR__'])
elif '__INTEL_COMPILER' in defines:
compiler = INTEL
version = int(defines['__INTEL_COMPILER'])
major = int(version / 100)
minor = int(version % 100)
patch = int(defines['__INTEL_COMPILER_UPDATE'])
elif '__clang__' in defines:
compiler = APPLE_CLANG if '__apple_build_version__' in defines else CLANG
major = int(defines['__clang_major__'])
minor = int(defines['__clang_minor__'])
patch = int(defines['__clang_patchlevel__'])
elif '__SUNPRO_C' in defines or '__SUNPRO_CC' in defines:
# In particular, the value of __SUNPRO_CC, which is a three-digit hex number.
# The first digit is the major release. The second digit is the minor release.
# The third digit is the micro release. For example, C++ 5.9 is 0x590.
compiler = SUNCC
define = '__SUNPRO_C' if '__SUNPRO_C' in defines else '__SUNPRO_CC'
version = int(defines[define], 16)
major = (version >> 8) & 0xF
minor = (version >> 4) & 0xF
patch = version & 0xF
# MSVC goes after Clang and Intel, as they may define _MSC_VER
elif '_MSC_VER' in defines:
compiler = MSVC
version = int(defines['_MSC_VER'])
# map _MSC_VER into conan-friendly Visual Studio version
# currently, conan uses major only, but here we store minor for the future as well
# https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=vs-2019
major, minor = MSVC_TO_VS_VERSION.get(version)
# special cases 19.8 and 19.9, 19.10 and 19.11
full_version = 0
if '_MSC_FULL_VER' in defines:
full_version = int(defines['_MSC_FULL_VER'])
if (major, minor) == (16, 8) and full_version >= 192829500:
major, minor = 16, 9
if (major, minor) == (16, 10) and full_version >= 192930100:
major, minor = 16, 11
patch = 0
# GCC must be the last try, as other compilers may define __GNUC__ for compatibility
elif '__GNUC__' in defines:
if '__llvm__' in defines:
compiler = LLVM_GCC
elif '__QNX__' in defines:
compiler = QCC
else:
compiler = GCC
major = int(defines['__GNUC__'])
minor = int(defines['__GNUC_MINOR__'])
patch = int(defines['__GNUC_PATCHLEVEL__'])
else:
return UNKNOWN_COMPILER
return CompilerId(compiler, major, minor, patch)
except KeyError:
return UNKNOWN_COMPILER
except ValueError:
return UNKNOWN_COMPILER
except TypeError:
return UNKNOWN_COMPILER
def detect_compiler_id(executable, runner=None):
runner = runner or ConanRunner()
# use a temporary file, as /dev/null might not be available on all platforms
tmpname = tempfile.mktemp(suffix=".c")
with open(tmpname, "wb") as f:
f.write(b"\n")
cmd = tempfile.mktemp(suffix=".cmd")
with open(cmd, "wb") as f:
f.write(b"echo off\nset MSC_CMD_FLAGS\n")
detectors = [
# "-dM" generate list of #define directives
# "-E" run only preprocessor
# "-x c" compiler as C code
# the output is of lines in form of "#define name value"
"-dM -E -x c",
"--driver-mode=g++ -dM -E -x c", # clang-cl
"-c -xdumpmacros", # SunCC,
# cl (Visual Studio, MSVC)
# "/nologo" Suppress Startup Banner
# "/E" Preprocess to stdout
# "/B1" C front-end
# "/c" Compile Without Linking
# "/TC" Specify Source File Type
'/nologo /E /B1 "%s" /c /TC' % cmd,
"/QdM /E /TC" # icc (Intel) on Windows,
"-Wp,-dM -E -x c" # QNX QCC
]
try:
for detector in detectors:
command = '%s %s "%s"' % (executable, detector, tmpname)
result = StringIO()
if 0 == runner(command, output=result):
output = result.getvalue()
defines = dict()
for line in output.splitlines():
tokens = line.split(' ', 3)
if len(tokens) == 3 and tokens[0] == '#define':
defines[tokens[1]] = tokens[2]
# MSVC dumps macro definitions in single line:
# "MSC_CMD_FLAGS=-D_MSC_VER=1921 -Ze"
elif line.startswith("MSC_CMD_FLAGS="):
line = line[len("MSC_CMD_FLAGS="):].rstrip()
defines = dict()
tokens = line.split()
for token in tokens:
if token.startswith("-D") or token.startswith("/D"):
token = token[2:]
if '=' in token:
name, value = token.split('=', 2)
else:
name, value = token, '1'
defines[name] = value
break
compiler = _parse_compiler_version(defines)
if compiler == UNKNOWN_COMPILER:
continue
return compiler
return UNKNOWN_COMPILER
finally:
os.unlink(tmpname)
os.unlink(cmd)