conans/client/conf/compiler_id.py
import os
import tempfile
from six import StringIO
from conans.client.runner import ConanRunner
from conans.model.version import Version
from conans.util.files import rmdir
GCC = "gcc"
LLVM_GCC = "llvm-gcc" # GCC frontend with LLVM backend
CLANG = "clang"
APPLE_CLANG = "apple-clang"
SUNCC = "suncc"
VISUAL_STUDIO = "Visual Studio"
INTEL = "intel"
QCC = "qcc"
MCST_LCC = "mcst-lcc"
MSVC = "msvc"
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:
version = int(defines['_MSC_VER'])
full_version = 0
if '_MSC_FULL_VER' in defines:
full_version = int(defines['_MSC_FULL_VER'])
# Visual Studio 2022 onwards, detect as a new compiler "msvc"
if version >= 1930:
compiler = MSVC
major = int(version / 100)
minor = int(version % 100)
patch = int(full_version % 100000)
else:
compiler = VISUAL_STUDIO
# 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
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
tmpdir = tempfile.mkdtemp()
tmpname = os.path.join(tmpdir, "temp.c")
with open(tmpname, "wb") as f:
f.write(b"\n")
cmd = os.path.join(tmpdir, "file.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:
try:
rmdir(tmpdir)
except OSError:
pass