Skip to content

Commit

Permalink
- improve compiler detection
Browse files Browse the repository at this point in the history
Signed-off-by: SSE4 <tomskside@gmail.com>
  • Loading branch information
SSE4 committed Sep 13, 2019
1 parent 6bc71d6 commit f655546
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 91 deletions.
204 changes: 204 additions & 0 deletions conans/client/build/compiler_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-

import os
import tempfile
from six import StringIO

from conans.client.runner import ConanRunner


GCC = "gcc"
LLVM_GCC = "llvm-gcc" # GCC frontend with LLVM backend
CLANG = "clang"
APPLE_CLANG = "apple-clang"
SUNCC = "suncc"
MSVC = "Visual Studio"
INTEL = "intel"


class CompilerId:
"""
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

@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.major == other.major and \
self.minor == other.minor and \
self.patch == other.patch

def __ne__(self, other):
return not self.__eq__(other)


UNKWNON_COMPILER = CompilerId(None, None, None, None)

MSC_VER = {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)}

def _parse_compiler_version(defines):
try:
if '__INTEL_COMPILER' in defines:
compiler = INTEL
version = int(defines['__INTEL_COMPILER'])
major = version / 100
minor = 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 = MSC_VER.get(version)
patch = 0
# GCC must be the last try, as other compilers may define __GNUC__ for compatibility
elif '__GNUC__' in defines:
compiler = LLVM_GCC if '__llvm__' in defines else GCC
major = int(defines['__GNUC__'])
minor = int(defines['__GNUC_MINOR__'])
patch = int(defines['__GNUC_PATCHLEVEL__'])
else:
return UNKWNON_COMPILER
return CompilerId(compiler, major, minor, patch)
except KeyError:
return UNKWNON_COMPILER
except ValueError:
return UNKWNON_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
]
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.split('\n'):
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"
if line.startswith("MSC_CMD_FLAGS="):
line = line[len("MSC_CMD_FLAGS="):]
if line.endswith("\r"):
line = line[:-1]
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
return _parse_compiler_version(defines)
return UNKWNON_COMPILER
finally:
os.unlink(tmpname)
os.unlink(cmd)
106 changes: 33 additions & 73 deletions conans/client/conf/detect.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os
import platform
import re
from subprocess import PIPE, Popen, STDOUT

from conans.client.build.compiler_id import UNKWNON_COMPILER, LLVM_GCC, detect_compiler_id
from conans.client.output import Color
from conans.client.tools import detected_os, OSInfo
from conans.client.tools.win import latest_visual_studio_version_installed
Expand All @@ -24,97 +24,53 @@ def _execute(command):
return proc.returncode, "".join(output_buffer)


def _gcc_compiler(output, compiler_exe="gcc"):

try:
if platform.system() == "Darwin":
# In Mac OS X check if gcc is a fronted using apple-clang
_, out = _execute("%s --version" % compiler_exe)
out = out.lower()
if "clang" in out:
return None

ret, out = _execute('%s -dumpversion' % compiler_exe)
if ret != 0:
return None
compiler = "gcc"
installed_version = re.search("([0-9](\.[0-9])?)", out).group()
# Since GCC 7.1, -dumpversion return the major version number
# only ("7"). We must use -dumpfullversion to get the full version
# number ("7.1.1").
if installed_version:
output.success("Found %s %s" % (compiler, installed_version))
return compiler, installed_version
except Exception:
return None


def _clang_compiler(output, compiler_exe="clang"):
try:
ret, out = _execute('%s --version' % compiler_exe)
if ret != 0:
return None
if "Apple" in out:
compiler = "apple-clang"
elif "clang version" in out:
compiler = "clang"
installed_version = re.search("([0-9]+\.[0-9])", out).group()
if installed_version:
output.success("Found %s %s" % (compiler, installed_version))
return compiler, installed_version
except Exception:
return None


def _sun_cc_compiler(output, compiler_exe="cc"):
try:
_, out = _execute('%s -V' % compiler_exe)
compiler = "sun-cc"
installed_version = re.search("([0-9]+\.[0-9]+)", out).group()
if installed_version:
output.success("Found %s %s" % (compiler, installed_version))
return compiler, installed_version
except Exception:
def _get_compiler_and_version(output, compiler_exe):
compiler_id = detect_compiler_id(compiler_exe)
if compiler_id.name == LLVM_GCC:
output.error("%s detected as a frontend using apple-clang. "
"Compiler not supported" % compiler_exe)
return None

if compiler_id != UNKWNON_COMPILER:
output.success("Found %s %s" % (compiler_id.name, compiler_id.major_minor))
return compiler_id.name, compiler_id.major_minor
return None

def _get_default_compiler(output):
"""
find the default compiler on the build machine
search order and priority:
1. CC and CXX environment variables are always top priority
2. Visual Studio detection (Windows only) via vswhere or registry or environment variables
3. Apple Clang (Mac only)
4. cc executable
5. gcc executable
6. clang executable
"""
cc = os.environ.get("CC", "")
cxx = os.environ.get("CXX", "")
if cc or cxx: # Env defined, use them
output.info("CC and CXX: %s, %s " % (cc or "None", cxx or "None"))
command = cc or cxx
if "gcc" in command:
gcc = _gcc_compiler(output, command)
if platform.system() == "Darwin" and gcc is None:
output.error(
"%s detected as a frontend using apple-clang. Compiler not supported" % command
)
return gcc
if "clang" in command.lower():
return _clang_compiler(output, command)
if platform.system() == "SunOS" and command.lower() == "cc":
return _sun_cc_compiler(output, command)
compiler = _get_compiler_and_version(output, command)
if compiler:
return compiler
# I am not able to find its version
output.error("Not able to automatically detect '%s' version" % command)
return None

if detected_os() == "Windows":
version = latest_visual_studio_version_installed(output)
vs = ('Visual Studio', version) if version else None
gcc = _gcc_compiler(output)
clang = _clang_compiler(output)
if platform.system() == "SunOS":
sun_cc = _sun_cc_compiler(output)
cc = _get_compiler_and_version(output, "cc")
gcc = _get_compiler_and_version(output, "gcc")
clang = _get_compiler_and_version(output, "clang")

if detected_os() == "Windows":
return vs or gcc or clang
return vs or cc or gcc or clang
elif platform.system() == "Darwin":
return clang or gcc
elif platform.system() == "SunOS":
return sun_cc or gcc or clang
return clang or cc or gcc
else:
return gcc or clang
return cc or gcc or clang


def _get_profile_compiler_version(compiler, version, output):
Expand All @@ -125,6 +81,10 @@ def _get_profile_compiler_version(compiler, version, output):
elif compiler == "gcc" and int(major) >= 5:
output.info("gcc>=5, using the major as version")
return major
elif compiler == "Visual Studio":
return major
elif compiler == "intel":
return major
return version


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def run(self, *args, **kwargs):
class RunnerMockWithHelp(RunnerMock):

def __init__(self, return_ok=True, available_args=None):
self.output = None
self.command_called = None
self.return_ok = return_ok
self.available_args = available_args or []
Expand Down
Loading

0 comments on commit f655546

Please sign in to comment.