diff --git a/conan/cli/commands/profile.py b/conan/cli/commands/profile.py index db52b830b75..bb3254fe523 100644 --- a/conan/cli/commands/profile.py +++ b/conan/cli/commands/profile.py @@ -22,11 +22,6 @@ def profiles_list_cli_output(profiles): cli_out_write(p) -def detected_profile_cli_output(detect_profile): - cli_out_write("Detected profile:") - cli_out_write(detect_profile.dumps()) - - @conan_subcommand(formatters={"text": print_profiles}) def profile_show(conan_api, parser, subparser, *args): """ @@ -63,7 +58,9 @@ def profile_detect(conan_api, parser, subparser, *args): raise ConanException(f"Profile '{profile_pathname}' already exists") detected_profile = conan_api.profiles.detect() - detected_profile_cli_output(detected_profile) + ConanOutput().success("\nDetected profile:") + cli_out_write(detected_profile.dumps()) + contents = detected_profile.dumps() ConanOutput().warning("This profile is a guess of your environment, please check it.") if detected_profile.settings.get("os") == "Macos": diff --git a/conan/internal/api/detect_api.py b/conan/internal/api/detect_api.py new file mode 100644 index 00000000000..156cae8a62a --- /dev/null +++ b/conan/internal/api/detect_api.py @@ -0,0 +1,411 @@ +import os +import platform +import re +import tempfile +import textwrap + +from conan.api.output import ConanOutput +from conans.model.version import Version +from conans.util.files import save +from conans.util.runners import check_output_runner, detect_runner + + +def detect_os(): + the_os = platform.system() + if the_os == "Darwin": + the_os = "Macos" + return the_os + + +def detect_arch(): + machine = platform.machine() + arch = None + system = platform.system() + + # special detectors + if system == "SunOS": + arch = _get_solaris_architecture() + elif system == "AIX": + arch = _get_aix_architecture() + if arch: + return arch + + if "ppc64le" in machine: + return "ppc64le" + elif "ppc64" in machine: + return "ppc64" + elif "ppc" in machine: + return "ppc32" + elif "mips64" in machine: + return "mips64" + elif "mips" in machine: + return "mips" + elif "sparc64" in machine: + return "sparcv9" + elif "sparc" in machine: + return "sparc" + elif "aarch64" in machine: + return "armv8" + elif "arm64" in machine: + return "armv8" + elif "ARM64" in machine: + return "armv8" + elif "64" in machine: + return "x86_64" + elif "86" in machine: + return "x86" + elif "armv8" in machine: + return "armv8" + elif "armv7" in machine: + return "armv7" + elif "arm" in machine: + return "armv6" + elif "s390x" in machine: + return "s390x" + elif "s390" in machine: + return "s390" + elif "sun4v" in machine: + return "sparc" + elif "e2k" in machine: + return _get_e2k_architecture() + + return None + + +def _get_solaris_architecture(): + # under intel solaris, platform.machine()=='i86pc' so we need to handle + # it early to suport 64-bit + processor = platform.processor() + kernel_bitness, elf = platform.architecture() + if "sparc" in processor: + return "sparcv9" if kernel_bitness == "64bit" else "sparc" + elif "i386" in processor: + return "x86_64" if kernel_bitness == "64bit" else "x86" + + +def _get_aix_conf(options=None): + options = " %s" % options if options else "" + try: + ret = check_output_runner("getconf%s" % options).strip() + return ret + except Exception as e: + ConanOutput(scope="detect_api").warning(f"Couldn't get aix getconf {e}") + return None + + +def _get_aix_architecture(): + processor = platform.processor() + if "powerpc" in processor: + kernel_bitness = _get_aix_conf("KERNEL_BITMODE") + if kernel_bitness: + return "ppc64" if kernel_bitness == "64" else "ppc32" + elif "rs6000" in processor: + return "ppc32" + + +def _get_e2k_architecture(): + return { + "E1C+": "e2k-v4", # Elbrus 1C+ and Elbrus 1CK + "E2C+": "e2k-v2", # Elbrus 2CM + "E2C+DSP": "e2k-v2", # Elbrus 2C+ + "E2C3": "e2k-v6", # Elbrus 2C3 + "E2S": "e2k-v3", # Elbrus 2S (aka Elbrus 4C) + "E8C": "e2k-v4", # Elbrus 8C and Elbrus 8C1 + "E8C2": "e2k-v5", # Elbrus 8C2 (aka Elbrus 8CB) + "E12C": "e2k-v6", # Elbrus 12C + "E16C": "e2k-v6", # Elbrus 16C + "E32C": "e2k-v7", # Elbrus 32C + }.get(platform.processor()) + + +def detect_libcxx(compiler, version): + assert isinstance(version, Version) + + def _detect_gcc_libcxx(version_, executable): + output = ConanOutput(scope="detect_api") + # Assumes a working g++ executable + if executable == "g++": # we can rule out old gcc versions + new_abi_available = version_ >= "5.1" + if not new_abi_available: + return "libstdc++" + + main = textwrap.dedent(""" + #include + + 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.warning("compiler.libcxx check error: %s" % out_str) + output.warning("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) + + # This is not really a detection in most cases + # Get compiler C++ stdlib + + if compiler == "apple-clang": + return "libc++" + elif compiler == "gcc": + libcxx = _detect_gcc_libcxx(version, "g++") + return libcxx + elif compiler == "cc": + if platform.system() == "SunOS": + return "libstdcxx4" + elif compiler == "clang": + if platform.system() == "FreeBSD": + return "libc++" + elif platform.system() == "Darwin": + return "libc++" + elif platform.system() == "Windows": + return # by default windows will assume LLVM/Clang with VS backend + else: # Linux + libcxx = _detect_gcc_libcxx(version, "clang++") + return libcxx + elif compiler == "sun-cc": + return "libCstd" + elif compiler == "mcst-lcc": + return "libstdc++" + + +def default_msvc_runtime(compiler): + if platform.system() != "Windows": + return None, None + if compiler == "clang": + # It could be LLVM/Clang with VS runtime or Msys2 with libcxx + ConanOutput(scope="detect_api").warning("Assuming LLVM/Clang in Windows with VS 17 2022") + ConanOutput(scope="detect_api").warning("If Msys2/Clang need to remove compiler.runtime* " + "and define compiler.libcxx") + return "dynamic", "v143" + elif compiler == "msvc": + # Add default mandatory fields for MSVC compiler + return "dynamic", None + return None, None + + +def default_cppstd(compiler, compiler_version): + """ returns the default cppstd for the compiler-version. This is not detected, just the default + """ + def _clang_cppstd_default(version): + if version >= "16": + return "gnu17" + # Official docs are wrong, in 6.0 the default is gnu14 to follow gcc's choice + return "gnu98" if version < "6" else "gnu14" + + def _gcc_cppstd_default(version): + if version >= "11": + return "gnu17" + return "gnu98" if version < "6" else "gnu14" + + def _visual_cppstd_default(version): + if version >= "190": # VS 2015 update 3 only + return "14" + return None + + def _mcst_lcc_cppstd_default(version): + return "gnu14" if version >= "1.24" else "gnu98" + + default = {"gcc": _gcc_cppstd_default(compiler_version), + "clang": _clang_cppstd_default(compiler_version), + "apple-clang": "gnu98", + "msvc": _visual_cppstd_default(compiler_version), + "mcst-lcc": _mcst_lcc_cppstd_default(compiler_version)}.get(str(compiler), None) + return default + + +def detect_compiler(): + """ + 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 + """ + output = ConanOutput(scope="detect_api") + 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 "clang" in command.lower(): + return _clang_compiler(command) + if "gcc" in command or "g++" in command or "c++" in command: + gcc, gcc_version = _gcc_compiler(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, gcc_version + if platform.system() == "SunOS" and command.lower() == "cc": + return _sun_cc_compiler(command) + if platform.system() == "Windows" and command.rstrip('"').endswith(("cl", "cl.exe")) \ + and "clang" not in command: + return _msvc_cl_compiler(command) + + # I am not able to find its version + output.error("Not able to automatically detect '%s' version" % command) + return None, None + + if platform.system() == "Windows": + version = _detect_vs_ide_version() + version = {"17": "193", "16": "192", "15": "191"}.get(str(version)) # Map to compiler + if version: + return 'msvc', Version(version) + + if platform.system() == "SunOS": + sun_cc, sun_cc_version = _sun_cc_compiler() + if sun_cc: + return sun_cc, sun_cc_version + + if platform.system() in ["Darwin", "FreeBSD"]: + clang, clang_version = _clang_compiler() # prioritize clang + if clang: + return clang, clang_version + return + else: + gcc, gcc_version = _gcc_compiler() + if gcc: + return gcc, gcc_version + return _clang_compiler() + + +def default_msvc_ide_version(version): + version = {"193": "17", "192": "16", "191": "15"}.get(str(version)) + if version: + return Version(version) + + +def _detect_vs_ide_version(): + from conans.client.conf.detect_vs import vs_installation_path + msvc_versions = "17", "16", "15" + for version in msvc_versions: + vs_path = os.getenv('vs%s0comntools' % version) + path = vs_path or vs_installation_path(version) + if path: + ConanOutput(scope="detect_api").info("Found msvc %s" % version) + return Version(version) + return None + + +def _gcc_compiler(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, None + + ret, out = detect_runner('%s -dumpversion' % compiler_exe) + if ret != 0: + return None, 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: + ConanOutput(scope="detect_api").info("Found %s %s" % (compiler, installed_version)) + return compiler, Version(installed_version) + except (Exception,): # to disable broad-except + return None, None + + +def _sun_cc_compiler(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: + ConanOutput(scope="detect_api").info("Found %s %s" % (compiler, installed_version)) + return compiler, Version(installed_version) + except (Exception,): # to disable broad-except + return None, None + + +def _clang_compiler(compiler_exe="clang"): + try: + ret, out = detect_runner('%s --version' % compiler_exe) + if ret != 0: + return None, None + if "Apple" in out: + compiler = "apple-clang" + elif "clang version" in out: + compiler = "clang" + else: + return None, None + installed_version = re.search(r"([0-9]+\.[0-9])", out).group() + if installed_version: + ConanOutput(scope="detect_api").info("Found %s %s" % (compiler, installed_version)) + return compiler, Version(installed_version) + except (Exception,): # to disable broad-except + return None, None + + +def _msvc_cl_compiler(compiler_exe="cl"): + """ only if CC/CXX env-vars are defined pointing to cl.exe, and the VS environment must + be active to have them in the path + """ + try: + compiler_exe = compiler_exe.strip('"') + ret, out = detect_runner(f'"{compiler_exe}" /?') + if ret != 0: + return None, None + first_line = out.splitlines()[0] + if "Microsoft" not in first_line: + return None, None + compiler = "msvc" + version_regex = re.search(r"(?P[0-9]+)\.(?P[0-9]+)\.([0-9]+)\.?([0-9]+)?", + first_line) + if not version_regex: + return None, None + # 19.36.32535 -> 193 + version = f"{version_regex.group('major')}{version_regex.group('minor')[0]}" + return compiler, Version(version) + except (Exception,): # to disable broad-except + return None, None + + +def default_compiler_version(compiler, version): + """ returns the default version that Conan uses in profiles, typically dropping some + of the minor or patch digits, that do not affect binary compatibility + """ + output = ConanOutput(scope="detect_api") + tokens = version.main + major = tokens[0] + minor = tokens[1] if len(tokens) > 1 else 0 + if compiler == "clang" and major >= 8: + output.info("clang>=8, using the major as version") + return major + elif compiler == "gcc" and major >= 5: + output.info("gcc>=5, using the major as version") + return major + elif compiler == "apple-clang" and major >= 13: + output.info("apple-clang>=13, using the major as version") + return major + elif compiler == "intel" and (major < 19 or (major == 19 and minor == 0)): + return major + elif compiler == "msvc": + return major + return version diff --git a/conan/tools/build/cppstd.py b/conan/tools/build/cppstd.py index 7a62aa6f72b..01ef4d71f45 100644 --- a/conan/tools/build/cppstd.py +++ b/conan/tools/build/cppstd.py @@ -1,6 +1,7 @@ import operator from conan.errors import ConanInvalidConfiguration, ConanException +from conan.internal.api.detect_api import default_cppstd as default_cppstd_ from conans.model.version import Version @@ -88,8 +89,7 @@ def default_cppstd(conanfile, compiler=None, compiler_version=None): compiler_version = compiler_version or conanfile.settings.get_safe("compiler.version") if not compiler or not compiler_version: raise ConanException("Called default_cppstd with no compiler or no compiler.version") - from conans.client.conf.detect import _cppstd_default - return _cppstd_default(compiler, Version(compiler_version)) + return default_cppstd_(compiler, Version(compiler_version)) def supported_cppstd(conanfile, compiler=None, compiler_version=None): diff --git a/conans/client/cache/cache.py b/conans/client/cache/cache.py index 6a6e4f9fd28..8c513cb8858 100644 --- a/conans/client/cache/cache.py +++ b/conans/client/cache/cache.py @@ -8,6 +8,7 @@ from conan import conan_version from conan.api.output import ConanOutput +from conan.internal.api import detect_api from conan.internal.cache.cache import DataCache, RecipeLayout, PackageLayout from conans.client.cache.editable import EditablePackages from conans.client.cache.remote_registry import RemoteRegistry @@ -168,7 +169,8 @@ def new_config(self): template = Environment(loader=FileSystemLoader(self.cache_folder)).from_string(text) content = template.render({"platform": platform, "os": os, "distro": distro, "conan_version": conan_version, - "conan_home_folder": self.cache_folder}) + "conan_home_folder": self.cache_folder, + "detect_api": detect_api}) self._new_config.loads(content) else: # creation of a blank global.conf file for user convenience default_global_conf = textwrap.dedent("""\ diff --git a/conans/client/conf/detect.py b/conans/client/conf/detect.py index 6354b9fd71c..f42e18b5c7c 100644 --- a/conans/client/conf/detect.py +++ b/conans/client/conf/detect.py @@ -1,439 +1,38 @@ -import os -import platform -import re -import tempfile -import textwrap - from conan.api.output import ConanOutput -from conans.client.conf.detect_vs import latest_visual_studio_version_installed -from conans.model.version import Version -from conans.util.files import save -from conans.util.runners import detect_runner, check_output_runner - - -def _gcc_compiler(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: - ConanOutput().success("Found %s %s" % (compiler, installed_version)) - return compiler, installed_version - except Exception: - return None - -def _msvc_cl_compiler(compiler_exe="cl"): - try: - compiler_exe = compiler_exe.strip('"') - ret, out = detect_runner(f'"{compiler_exe}" /?') - if ret != 0: - return None - first_line = out.splitlines()[0] - if not "Microsoft" in first_line: - return None - compiler = "msvc" - version_regex = re.search(r"(?P[0-9]+)\.(?P[0-9]+)\.([0-9]+)\.?([0-9]+)?", first_line) - if not version_regex: - return None - # 19.36.32535 -> 193 - version = f"{version_regex.group('major')}{version_regex.group('minor')[0]}" - return (compiler, version) - except Exception: - return None +from conan.internal.api.detect_api import detect_os, detect_arch, default_msvc_runtime, \ + detect_libcxx, default_cppstd, detect_compiler, default_compiler_version -def _clang_compiler(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: - ConanOutput().success("Found %s %s" % (compiler, installed_version)) - return compiler, installed_version - except Exception: - return None - - -def _sun_cc_compiler(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: - ConanOutput().success("Found %s %s" % (compiler, installed_version)) - return compiler, installed_version - except Exception: - return None - - -def _get_default_compiler(): - """ - 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 +def detect_defaults_settings(): + """ try to deduce current machine values without any constraints at all + :return: A list with default settings """ - output = ConanOutput() - 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 "clang" in command.lower(): - return _clang_compiler(command) - if "gcc" in command or "g++" in command or "c++" in command: - gcc = _gcc_compiler(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(command) - if platform.system() == "Windows" and command.rstrip('"').endswith(("cl", "cl.exe")) and not "clang" in command: - return _msvc_cl_compiler(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 platform.system() == "Windows": - version = latest_visual_studio_version_installed() - vs = ('msvc', version) if version else None - - gcc = _gcc_compiler() - clang = _clang_compiler() - if platform.system() == "SunOS": - sun_cc = _sun_cc_compiler() - - if platform.system() == "Windows": - return vs or cc or gcc or clang - elif platform.system() in ["Darwin", "FreeBSD"]: - 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 = ConanOutput() - tokens = version.main - major = tokens[0] - minor = tokens[1] if len(tokens) > 1 else 0 - if compiler == "clang" and major >= 8: - output.info("clang>=8, using the major as version") - return major - elif compiler == "gcc" and major >= 5: - output.info("gcc>=5, using the major as version") - return major - elif compiler == "apple-clang" and major >= 13: - output.info("apple-clang>=13, using the major as version") - return major - elif compiler == "intel" and (major < 19 or (major == 19 and minor == 0)): - return major - elif compiler == "msvc": - return major - - return version - - -def _detect_gcc_libcxx(version, executable): - output = ConanOutput() - # Assumes a working g++ executable - if executable == "g++": # we can rule out old gcc versions - new_abi_available = version >= "5.1" - if not new_abi_available: - return "libstdc++" - - main = textwrap.dedent(""" - #include - - 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.warning("compiler.libcxx check error: %s" % out_str) - output.warning("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): - try: - compiler, version = _get_default_compiler() - except Exception: - compiler, version = None, None - if not compiler or not version: - ConanOutput().info("No compiler was detected (one may not be needed)") - return - - version = Version(version) - - result.append(("compiler", compiler)) - result.append(("compiler.version", _get_profile_compiler_version(compiler, version))) - - # Get compiler C++ stdlib - if compiler == "apple-clang": - result.append(("compiler.libcxx", "libc++")) - elif compiler == "gcc": - libcxx = _detect_gcc_libcxx(version, "g++") - 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++")) - elif platform.system() == "Darwin": - result.append(("compiler.libcxx", "libc++")) - elif platform.system() == "Windows": - # It could be LLVM/Clang with VS runtime or Msys2 with libcxx - result.append(("compiler.runtime", "dynamic")) - result.append(("compiler.runtime_type", "Release")) - result.append(("compiler.runtime_version", "v143")) - ConanOutput().warning("Assuming LLVM/Clang in Windows with VS 17 2022") - ConanOutput().warning("If Msys2/Clang need to remove compiler.runtime* and " - "define compiler.libcxx") - else: - libcxx = _detect_gcc_libcxx(version, "clang++") - result.append(("compiler.libcxx", libcxx)) - elif compiler == "sun-cc": - result.append(("compiler.libcxx", "libCstd")) - elif compiler == "mcst-lcc": - result.append(("compiler.libcxx", "libstdc++")) - elif compiler == "msvc": - # Add default mandatory fields for MSVC compiler - result.append(("compiler.cppstd", "14")) - result.append(("compiler.runtime", "dynamic")) - - if compiler != "msvc": - cppstd = _cppstd_default(compiler, version) - if compiler == "apple-clang" and version >= "11": - # forced auto-detection, gnu98 is too old - cppstd = "gnu17" - result.append(("compiler.cppstd", cppstd)) - - -def _get_solaris_architecture(): - # under intel solaris, platform.machine()=='i86pc' so we need to handle - # it early to suport 64-bit - processor = platform.processor() - kernel_bitness, elf = platform.architecture() - if "sparc" in processor: - return "sparcv9" if kernel_bitness == "64bit" else "sparc" - elif "i386" in processor: - return "x86_64" if kernel_bitness == "64bit" else "x86" - - -def _get_aix_conf(options=None): - options = " %s" % options if options else "" - try: - ret = check_output_runner("getconf%s" % options).strip() - return ret - except Exception: - return None - - -def _get_aix_architecture(): - processor = platform.processor() - if "powerpc" in processor: - kernel_bitness = _get_aix_conf("KERNEL_BITMODE") - if kernel_bitness: - return "ppc64" if kernel_bitness == "64" else "ppc32" - elif "rs6000" in processor: - return "ppc32" - - -def _get_e2k_architecture(): - return { - "E1C+": "e2k-v4", # Elbrus 1C+ and Elbrus 1CK - "E2C+": "e2k-v2", # Elbrus 2CM - "E2C+DSP": "e2k-v2", # Elbrus 2C+ - "E2C3": "e2k-v6", # Elbrus 2C3 - "E2S": "e2k-v3", # Elbrus 2S (aka Elbrus 4C) - "E8C": "e2k-v4", # Elbrus 8C and Elbrus 8C1 - "E8C2": "e2k-v5", # Elbrus 8C2 (aka Elbrus 8CB) - "E12C": "e2k-v6", # Elbrus 12C - "E16C": "e2k-v6", # Elbrus 16C - "E32C": "e2k-v7", # Elbrus 32C - }.get(platform.processor()) - - -def _detected_architecture(): - # FIXME: Very weak check but not very common to run conan in other architectures - machine = platform.machine() - arch = None - system = platform.system() - - # special detectors - if system == "SunOS": - arch = _get_solaris_architecture() - elif system == "AIX": - arch = _get_aix_architecture() - if arch: - return arch - - if "ppc64le" in machine: - return "ppc64le" - elif "ppc64" in machine: - return "ppc64" - elif "ppc" in machine: - return "ppc32" - elif "mips64" in machine: - return "mips64" - elif "mips" in machine: - return "mips" - elif "sparc64" in machine: - return "sparcv9" - elif "sparc" in machine: - return "sparc" - elif "aarch64" in machine: - return "armv8" - elif "ARM64" in machine: - return "armv8" - elif "arm64" in machine: - return "armv8" - elif "64" in machine: - return "x86_64" - elif "86" in machine: - return "x86" - elif "armv8" in machine: - return "armv8" - elif "armv7" in machine: - return "armv7" - elif "arm" in machine: - return "armv6" - elif "s390x" in machine: - return "s390x" - elif "s390" in machine: - return "s390" - elif "sun4v" in machine: - return "sparc" - elif "e2k" in machine: - return _get_e2k_architecture() - - return None - - -def _detect_os_arch(result): - from conans.client.conf import default_settings_yml - from conans.model.settings import Settings - - the_os = platform.system() - if the_os == "Darwin": - the_os = "Macos" + result = [] + the_os = detect_os() result.append(("os", the_os)) - arch = _detected_architecture() - + arch = detect_arch() if arch: - if arch.startswith('arm'): - settings = Settings.loads(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: - ConanOutput().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)) + compiler, version = detect_compiler() + if not compiler: + result.append(("build_type", "Release")) + ConanOutput().warning("No compiler was detected (one may not be needed)") + return result - -def detect_defaults_settings(): - """ try to deduce current machine values without any constraints at all - :return: A list with default settings - """ - result = [] - _detect_os_arch(result) - _detect_compiler_version(result) + result.append(("compiler", compiler)) + result.append(("compiler.version", default_compiler_version(compiler, version))) + + runtime, runtime_version = default_msvc_runtime(compiler) + if runtime: + result.append(("compiler.runtime", runtime)) + if runtime_version: + result.append(("compiler.runtime_version", runtime_version)) + libcxx = detect_libcxx(compiler, version) + if libcxx: + result.append(("compiler.libcxx", libcxx)) + cppstd = default_cppstd(compiler, version) + if cppstd: + result.append(("compiler.cppstd", cppstd)) result.append(("build_type", "Release")) - return result - - -def _cppstd_default(compiler, compiler_version): - assert isinstance(compiler_version, Version) - default = {"gcc": _gcc_cppstd_default(compiler_version), - "clang": _clang_cppstd_default(compiler_version), - "apple-clang": "gnu98", - "msvc": _visual_cppstd_default(compiler_version), - "mcst-lcc": _mcst_lcc_cppstd_default(compiler_version)}.get(str(compiler), None) - return default - - -def _clang_cppstd_default(compiler_version): - if compiler_version >= "16": - return "gnu17" - # Official docs are wrong, in 6.0 the default is gnu14 to follow gcc's choice - return "gnu98" if compiler_version < "6" else "gnu14" - - -def _gcc_cppstd_default(compiler_version): - if compiler_version >= "11": - return "gnu17" - return "gnu98" if compiler_version < "6" else "gnu14" - - -def _visual_cppstd_default(compiler_version): - if compiler_version >= "190": # VS 2015 update 3 only - return "14" - return None - - -def _intel_visual_cppstd_default(_): - return None - - -def _intel_gcc_cppstd_default(_): - return "gnu98" - - -def _mcst_lcc_cppstd_default(compiler_version): - return "gnu14" if compiler_version >= "1.24" else "gnu98" diff --git a/conans/client/conf/detect_vs.py b/conans/client/conf/detect_vs.py index de5521f1e87..f8eb3eb9b6c 100644 --- a/conans/client/conf/detect_vs.py +++ b/conans/client/conf/detect_vs.py @@ -3,32 +3,10 @@ from shutil import which from conan.tools.build import cmd_args_to_string -from conan.api.output import ConanOutput from conans.errors import ConanException -from conans.model.version import Version from conans.util.env import get_env -def _visual_compiler(version): - """"version have to be 8.0, or 9.0 or... anything .0""" - if Version(version) >= "15": - vs_path = os.getenv('vs%s0comntools' % version) - path = vs_path or vs_installation_path(version) - if path: - ConanOutput().success("Found msvc %s" % version) - return version - return None - - -def latest_visual_studio_version_installed(): - msvc_sersions = "17", "16", "15" - for version in msvc_sersions: - vs = _visual_compiler(version) - if vs: - return {"17": "193", "16": "192", "15": "191"}.get(vs) - return None - - def vs_installation_path(version): # TODO: Preference hardcoded, [conf] must be defined preference = ["Enterprise", "Professional", "Community", "BuildTools"] diff --git a/conans/client/profile_loader.py b/conans/client/profile_loader.py index 11d9f4ae773..8968508acfb 100644 --- a/conans/client/profile_loader.py +++ b/conans/client/profile_loader.py @@ -5,6 +5,7 @@ from jinja2 import Environment, FileSystemLoader from conan import conan_version +from conan.internal.api import detect_api from conan.tools.env.environment import ProfileEnvironment from conans.client.loader import load_python_file from conans.errors import ConanException, scoped_traceback @@ -171,7 +172,8 @@ def _load_profile(self, profile_name, cwd): "os": os, "profile_dir": base_path, "profile_name": file_path, - "conan_version": conan_version} + "conan_version": conan_version, + "detect_api": detect_api} rtemplate = Environment(loader=FileSystemLoader(base_path)).from_string(text) text = rtemplate.render(context) diff --git a/conans/test/functional/test_profile_detect_api.py b/conans/test/functional/test_profile_detect_api.py new file mode 100644 index 00000000000..3c2b732389f --- /dev/null +++ b/conans/test/functional/test_profile_detect_api.py @@ -0,0 +1,39 @@ +import textwrap + +import pytest + +from conans.test.utils.tools import TestClient + + +class TestProfileDetectAPI: + @pytest.mark.tool("visual_studio", "17") + def test_profile_detect_compiler(self): + + client = TestClient() + tpl1 = textwrap.dedent(""" + {% set compiler, version = detect_api.detect_compiler() %} + {% set runtime, _ = detect_api.default_msvc_runtime(compiler) %} + [settings] + compiler={{compiler}} + compiler.version={{detect_api.default_compiler_version(compiler, version)}} + compiler.runtime={{runtime}} + compiler.cppstd={{detect_api.default_cppstd(compiler, version)}} + + [conf] + tools.microsoft.msbuild:vs_version={{detect_api.default_msvc_ide_version(version)}} + """) + + client.save({"profile1": tpl1}) + client.run("profile show -pr=profile1") + expected = textwrap.dedent(f"""\ + Host profile: + [settings] + compiler=msvc + compiler.cppstd=14 + compiler.runtime=dynamic + compiler.runtime_type=Release + compiler.version=193 + [conf] + tools.microsoft.msbuild:vs_version=17 + """) + assert expected in client.out diff --git a/conans/test/integration/configuration/conf/test_conf.py b/conans/test/integration/configuration/conf/test_conf.py index 51c14e1b0ea..2ab82ffd10d 100644 --- a/conans/test/integration/configuration/conf/test_conf.py +++ b/conans/test/integration/configuration/conf/test_conf.py @@ -6,6 +6,7 @@ from mock import patch from conan import conan_version +from conan.internal.api import detect_api from conans.errors import ConanException from conans.util.files import save, load from conans.test.utils.tools import TestClient @@ -225,6 +226,23 @@ def test_jinja_global_conf_paths(): assert f"user.mycompany:myfile: {os.path.join(c.cache_folder, 'myfile')}" in c.out +def test_profile_detect_os_arch(): + """ testing OS & ARCH just to test that detect_api is injected + """ + c = TestClient() + global_conf = textwrap.dedent(""" + user.myteam:myconf1={{detect_api.detect_os()}} + user.myteam:myconf2={{detect_api.detect_arch()}} + """) + + save(c.cache.new_config_path, global_conf) + c.run("config show *") + _os = detect_api.detect_os() + _arch = detect_api.detect_arch() + assert f"user.myteam:myconf1: {_os}" in c.out + assert f"user.myteam:myconf2: {_arch}" in c.out + + def test_empty_conf_valid(): tc = TestClient() profile = textwrap.dedent(r""" diff --git a/conans/test/integration/configuration/test_profile_jinja.py b/conans/test/integration/configuration/test_profile_jinja.py index 6c5d5970b7b..48b9a1862a5 100644 --- a/conans/test/integration/configuration/test_profile_jinja.py +++ b/conans/test/integration/configuration/test_profile_jinja.py @@ -161,3 +161,28 @@ def configure(self): # included profiles should respect the inherited profile name client.run("install . -pr=include_folder/include_default") assert "conanfile.py: PROFILE NAME: include_default" in client.out + + +class TestProfileDetectAPI: + def test_profile_detect_os_arch(self): + """ testing OS & ARCH just to test the UX and interface + """ + client = TestClient() + tpl1 = textwrap.dedent(""" + [settings] + os={{detect_api.detect_os()}} + arch={{detect_api.detect_arch()}} + """) + + client.save({"profile1": tpl1}) + client.run("profile show -pr=profile1") + pr = client.get_default_host_profile() + the_os = pr.settings['os'] + arch = pr.settings['arch'] + expected = textwrap.dedent(f"""\ + Host profile: + [settings] + arch={arch} + os={the_os} + """) + assert expected in client.out diff --git a/conans/test/unittests/client/build/cpp_std_flags_test.py b/conans/test/unittests/client/build/cpp_std_flags_test.py index 38049ba5995..1a7f97109fc 100644 --- a/conans/test/unittests/client/build/cpp_std_flags_test.py +++ b/conans/test/unittests/client/build/cpp_std_flags_test.py @@ -2,8 +2,8 @@ import pytest +from conan.internal.api.detect_api import default_cppstd from conan.tools.build.flags import cppstd_flag -from conans.client.conf.detect import _cppstd_default from conans.model.version import Version from conans.test.utils.mocks import MockSettings @@ -16,7 +16,7 @@ def _make_cppstd_flag(compiler, compiler_version, cppstd=None): def _make_cppstd_default(compiler, compiler_version): - return _cppstd_default(compiler, Version(compiler_version)) + return default_cppstd(compiler, Version(compiler_version)) class CompilerFlagsTest(unittest.TestCase): diff --git a/conans/test/unittests/client/conf/detect/test_gcc_compiler.py b/conans/test/unittests/client/conf/detect/test_gcc_compiler.py index a9812c87b84..39f3a6aa65b 100644 --- a/conans/test/unittests/client/conf/detect/test_gcc_compiler.py +++ b/conans/test/unittests/client/conf/detect/test_gcc_compiler.py @@ -3,8 +3,8 @@ import mock from parameterized import parameterized -from conans.client.conf.detect import _gcc_compiler -from conan.api.output import ConanOutput + +from conan.internal.api.detect_api import _gcc_compiler class GCCCompilerTestCase(unittest.TestCase): @@ -12,7 +12,7 @@ class GCCCompilerTestCase(unittest.TestCase): @parameterized.expand([("10",), ("4.2",), ('7', )]) def test_detect_gcc_10(self, version): with mock.patch("platform.system", return_value="Linux"): - with mock.patch("conans.client.conf.detect.detect_runner", return_value=(0, version)): - compiler, installed_version = _gcc_compiler(ConanOutput()) + with mock.patch("conan.internal.api.detect_api.detect_runner", return_value=(0, version)): + compiler, installed_version = _gcc_compiler() self.assertEqual(compiler, 'gcc') self.assertEqual(installed_version, version) diff --git a/conans/test/unittests/tools/build/test_cppstd.py b/conans/test/unittests/tools/build/test_cppstd.py index 63d4382b8e5..9078e322ef7 100644 --- a/conans/test/unittests/tools/build/test_cppstd.py +++ b/conans/test/unittests/tools/build/test_cppstd.py @@ -115,6 +115,7 @@ def test_supported_cppstd_qcc(compiler, compiler_version, values): sot = supported_cppstd(conanfile) assert sot == values + @pytest.mark.parametrize("compiler,compiler_version,result", [ ("gcc", "5", 'gnu98'), ("gcc", "8", "gnu14"), diff --git a/conans/test/unittests/util/detect_test.py b/conans/test/unittests/util/detect_test.py index 5f9957d017b..ab18a5d7f0a 100644 --- a/conans/test/unittests/util/detect_test.py +++ b/conans/test/unittests/util/detect_test.py @@ -4,6 +4,7 @@ from parameterized import parameterized from conans.client.conf.detect import detect_defaults_settings +from conans.model.version import Version from conans.test.utils.mocks import RedirectedTestOutput from conans.test.utils.tools import redirect_output from conans.util.env import environment_update @@ -25,7 +26,7 @@ def test_detect_aix(self, processor, bitness, version, expected_arch): with mock.patch("platform.machine", mock.MagicMock(return_value='XXXXXXXXXXXX')), \ mock.patch("platform.processor", mock.MagicMock(return_value=processor)), \ mock.patch("platform.system", mock.MagicMock(return_value='AIX')), \ - mock.patch("conans.client.conf.detect._get_aix_conf", mock.MagicMock(return_value=bitness)), \ + mock.patch("conan.internal.api.detect_api._get_aix_conf", mock.MagicMock(return_value=bitness)), \ mock.patch('subprocess.check_output', mock.MagicMock(return_value=version)): result = detect_defaults_settings() result = dict(result) @@ -47,18 +48,11 @@ def test_detect_arch(self, machine, expected_arch): result = dict(result) self.assertEqual(expected_arch, result['arch']) - @mock.patch("conans.client.conf.detect._clang_compiler", return_value=("clang", "9")) + @mock.patch("conan.internal.api.detect_api._clang_compiler", + return_value=("clang", Version("9"))) def test_detect_clang_gcc_toolchain(self, _): output = RedirectedTestOutput() with redirect_output(output): with environment_update({"CC": "clang-9 --gcc-toolchain=/usr/lib/gcc/x86_64-linux-gnu/9"}): detect_defaults_settings() self.assertIn("CC and CXX: clang-9 --gcc-toolchain", output) - - def test_vs2022(self): - with mock.patch("conans.client.conf.detect._get_default_compiler", - mock.MagicMock(return_value=("Visual Studio", "17"))): - result = detect_defaults_settings() - result = dict(result) - self.assertEqual('Visual Studio', result['compiler']) - self.assertEqual('17', result['compiler.version']) diff --git a/conans/test/unittests/util/detected_architecture_test.py b/conans/test/unittests/util/detected_architecture_test.py index b95d5e14738..6f9b3564d4c 100644 --- a/conans/test/unittests/util/detected_architecture_test.py +++ b/conans/test/unittests/util/detected_architecture_test.py @@ -1,14 +1,9 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 - - import mock import unittest from parameterized import parameterized -from conans.client.conf.detect import _detected_architecture +from conan.internal.api.detect_api import detect_arch class DetectedArchitectureTest(unittest.TestCase): @@ -38,22 +33,22 @@ class DetectedArchitectureTest(unittest.TestCase): def test_various(self, mocked_machine, expected_arch): with mock.patch("platform.machine", mock.MagicMock(return_value=mocked_machine)): - self.assertEqual(expected_arch, _detected_architecture(), "given '%s' expected '%s'" % (mocked_machine, expected_arch)) + self.assertEqual(expected_arch, detect_arch(), "given '%s' expected '%s'" % (mocked_machine, expected_arch)) def test_aix(self): with mock.patch("platform.machine", mock.MagicMock(return_value='00FB91F44C00')),\ mock.patch("platform.processor", mock.MagicMock(return_value='powerpc')),\ mock.patch("platform.system", mock.MagicMock(return_value='AIX')),\ - mock.patch("conans.client.conf.detect._get_aix_conf", mock.MagicMock(return_value='32')),\ + mock.patch("conan.internal.api.detect_api._get_aix_conf", mock.MagicMock(return_value='32')),\ mock.patch('subprocess.check_output', mock.MagicMock(return_value='7.1.0.0')): - self.assertEqual('ppc32', _detected_architecture()) + self.assertEqual('ppc32', detect_arch()) with mock.patch("platform.machine", mock.MagicMock(return_value='00FB91F44C00')),\ mock.patch("platform.processor", mock.MagicMock(return_value='powerpc')),\ mock.patch("platform.system", mock.MagicMock(return_value='AIX')),\ - mock.patch("conans.client.conf.detect._get_aix_conf", mock.MagicMock(return_value='64')),\ + mock.patch("conan.internal.api.detect_api._get_aix_conf", mock.MagicMock(return_value='64')),\ mock.patch('subprocess.check_output', mock.MagicMock(return_value='7.1.0.0')): - self.assertEqual('ppc64', _detected_architecture()) + self.assertEqual('ppc64', detect_arch()) def test_solaris(self): with mock.patch("platform.machine", mock.MagicMock(return_value='sun4v')),\ @@ -61,14 +56,14 @@ def test_solaris(self): mock.patch("platform.system", mock.MagicMock(return_value='SunOS')),\ mock.patch("platform.architecture", mock.MagicMock(return_value=('64bit', 'ELF'))),\ mock.patch("platform.release", mock.MagicMock(return_value='5.11')): - self.assertEqual('sparcv9', _detected_architecture()) + self.assertEqual('sparcv9', detect_arch()) with mock.patch("platform.machine", mock.MagicMock(return_value='i86pc')),\ mock.patch("platform.processor", mock.MagicMock(return_value='i386')),\ mock.patch("platform.system", mock.MagicMock(return_value='SunOS')),\ mock.patch("platform.architecture", mock.MagicMock(return_value=('64bit', 'ELF'))),\ mock.patch("platform.release", mock.MagicMock(return_value='5.11')): - self.assertEqual('x86_64', _detected_architecture()) + self.assertEqual('x86_64', detect_arch()) @parameterized.expand([ ["E1C+", "e2k-v4"], @@ -85,4 +80,4 @@ def test_solaris(self): def test_e2k(self, processor, expected_arch): with mock.patch("platform.machine", mock.MagicMock(return_value='e2k')), \ mock.patch("platform.processor", mock.MagicMock(return_value=processor)): - self.assertEqual(expected_arch, _detected_architecture()) + self.assertEqual(expected_arch, detect_arch())