conans/client/conf/detect.py
import os
import platform
import re
import tempfile
import textwrap
from conans.client.conf.compiler_id import UNKNOWN_COMPILER, LLVM_GCC, detect_compiler_id
from conans.client.output import Color
from conans.client.tools import detected_os, detected_architecture
from conans.client.tools.win import latest_visual_studio_version_installed
from conans.model.version import Version
from conans.util.conan_v2_mode import CONAN_V2_MODE_ENVVAR
from conans.util.env_reader import get_env
from conans.util.files import save
from conans.util.runners import detect_runner
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 != UNKNOWN_COMPILER:
output.success("Found %s %s" % (compiler_id.name, compiler_id.major_minor))
return compiler_id.name, compiler_id.major_minor
return None
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 = detect_runner("%s --version" % compiler_exe)
out = out.lower()
if "clang" in out:
return None
ret, out = detect_runner('%s -dumpversion' % compiler_exe)
if ret != 0:
return None
compiler = "gcc"
installed_version = re.search(r"([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 = detect_runner('%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(r"([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 = detect_runner('%s -V' % compiler_exe)
compiler = "sun-cc"
installed_version = re.search(r"Sun C.*([0-9]+\.[0-9]+)", out)
if installed_version:
installed_version = installed_version.group(1)
else:
installed_version = re.search(r"([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 _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
"""
v2_mode = get_env(CONAN_V2_MODE_ENVVAR, False)
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 v2_mode:
compiler = _get_compiler_and_version(output, command)
if compiler:
return compiler
else:
if "clang" in command.lower():
return _clang_compiler(output, command)
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 platform.system() == "SunOS" and command.lower() == "cc":
return _sun_cc_compiler(output, command)
# I am not able to find its version
output.error("Not able to automatically detect '%s' version" % command)
return None
vs = cc = sun_cc = None
if detected_os() == "Windows":
version = latest_visual_studio_version_installed(output)
vs = ('Visual Studio', version) if version else None
if v2_mode:
cc = _get_compiler_and_version(output, "cc")
gcc = _get_compiler_and_version(output, "gcc")
clang = _get_compiler_and_version(output, "clang")
else:
gcc = _gcc_compiler(output)
clang = _clang_compiler(output)
if platform.system() == "SunOS":
sun_cc = _sun_cc_compiler(output)
if detected_os() == "Windows":
return vs or cc or gcc or clang
elif platform.system() == "Darwin":
return clang or cc or gcc
elif platform.system() == "SunOS":
return sun_cc or cc or gcc or clang
else:
return cc or gcc or clang
def _get_profile_compiler_version(compiler, version, output):
tokens = version.split(".")
major = tokens[0]
minor = tokens[1] if len(tokens) > 1 else 0
if compiler == "clang" and int(major) >= 8:
output.info("clang>=8, using the major as version")
return major
elif compiler == "gcc" and int(major) >= 5:
output.info("gcc>=5, using the major as version")
return major
elif compiler == "apple-clang" and int(major) >= 13:
output.info("apple-clang>=13, using the major as version")
return major
elif compiler == "Visual Studio":
return major
elif compiler == "intel" and (int(major) < 19 or (int(major) == 19 and int(minor) == 0)):
return major
elif compiler == "msvc":
return major
return version
def _detect_gcc_libcxx(executable, version, output, profile_name, profile_path):
# Assumes a working g++ executable
new_abi_available = Version(version) >= Version("5.1")
if not new_abi_available:
return "libstdc++"
if not get_env(CONAN_V2_MODE_ENVVAR, False):
msg = textwrap.dedent("""
Conan detected a GCC version > 5 but has adjusted the 'compiler.libcxx' setting to
'libstdc++' for backwards compatibility.
Your compiler is likely using the new CXX11 ABI by default (libstdc++11).
If you want Conan to use the new ABI for the {profile} profile, run:
$ conan profile update settings.compiler.libcxx=libstdc++11 {profile}
Or edit '{profile_path}' and set compiler.libcxx=libstdc++11
""".format(profile=profile_name, profile_path=profile_path))
output.writeln("\n************************* WARNING: GCC OLD ABI COMPATIBILITY "
"***********************\n %s\n************************************"
"************************************************\n\n\n" % msg,
Color.BRIGHT_RED)
return "libstdc++"
main = textwrap.dedent("""
#include <string>
using namespace std;
static_assert(sizeof(std::string) != sizeof(void*), "using libstdc++");
int main(){}
""")
t = tempfile.mkdtemp()
filename = os.path.join(t, "main.cpp")
save(filename, main)
old_path = os.getcwd()
os.chdir(t)
try:
error, out_str = detect_runner("%s main.cpp -std=c++11" % executable)
if error:
if "using libstdc++" in out_str:
output.info("gcc C++ standard library: libstdc++")
return "libstdc++"
# Other error, but can't know, lets keep libstdc++11
output.warn("compiler.libcxx check error: %s" % out_str)
output.warn("Couldn't deduce compiler.libcxx for gcc>=5.1, assuming libstdc++11")
else:
output.info("gcc C++ standard library: libstdc++11")
return "libstdc++11"
finally:
os.chdir(old_path)
def _detect_compiler_version(result, output, profile_path):
try:
compiler, version = _get_default_compiler(output)
except Exception:
compiler, version = None, None
if not compiler or not version:
output.info("No compiler was detected (one may not be needed)")
return
result.append(("compiler", compiler))
result.append(("compiler.version", _get_profile_compiler_version(compiler, version, output)))
# Get compiler C++ stdlib
if compiler == "apple-clang":
result.append(("compiler.libcxx", "libc++"))
elif compiler == "gcc":
profile_name = os.path.basename(profile_path)
libcxx = _detect_gcc_libcxx("g++", version, output, profile_name, profile_path)
result.append(("compiler.libcxx", libcxx))
elif compiler == "cc":
if platform.system() == "SunOS":
result.append(("compiler.libstdcxx", "libstdcxx4"))
elif compiler == "clang":
if platform.system() == "FreeBSD":
result.append(("compiler.libcxx", "libc++"))
else:
result.append(("compiler.libcxx", "libstdc++"))
elif compiler == "sun-cc":
result.append(("compiler.libcxx", "libCstd"))
elif compiler == "mcst-lcc":
result.append(("compiler.base", "gcc")) # do the same for Intel?
result.append(("compiler.base.libcxx", "libstdc++"))
version = Version(version)
if version >= "1.24":
result.append(("compiler.base.version", "7.3"))
elif version >= "1.23":
result.append(("compiler.base.version", "5.5"))
elif version >= "1.21":
result.append(("compiler.base.version", "4.8"))
else:
result.append(("compiler.base.version", "4.4"))
elif compiler == "msvc":
# Add default mandatory fields for MSVC compiler
result.append(("compiler.cppstd", "14"))
result.append(("compiler.runtime", "dynamic"))
result.append(("compiler.runtime_type", "Release"))
def _detect_os_arch(result, output):
from conans.client.conf import get_default_settings_yml
from conans.model.settings import Settings
the_os = detected_os()
result.append(("os", the_os))
result.append(("os_build", the_os))
arch = detected_architecture()
if arch:
if arch.startswith('arm'):
settings = Settings.loads(get_default_settings_yml())
defined_architectures = settings.arch.values_range
defined_arm_architectures = [v for v in defined_architectures if v.startswith("arm")]
for a in defined_arm_architectures:
if arch.startswith(a):
arch = a
break
else:
output.error("Your ARM '%s' architecture is probably not defined in settings.yml\n"
"Please check your conan.conf and settings.yml files" % arch)
result.append(("arch", arch))
result.append(("arch_build", arch))
def detect_defaults_settings(output, profile_path):
""" try to deduce current machine values without any constraints at all
:param output: Conan Output instance
:param profile_path: Conan profile file path
:return: A list with default settings
"""
result = []
_detect_os_arch(result, output)
_detect_compiler_version(result, output, profile_path)
result.append(("build_type", "Release"))
return result