conans/client/tools/oss.py
import math
import multiprocessing
import os
import platform
import subprocess
import sys
import warnings
from collections import namedtuple
from conans.client.tools.env import environment_append
from conans.client.tools.files import load, which
from conans.errors import CalledProcessErrorWithStderr, ConanException
from conans.model.version import Version
from conans.util.runners import check_output_runner
def args_to_string(args):
if not args:
return ""
if sys.platform == 'win32':
return subprocess.list2cmdline(args)
else:
return " ".join("'" + arg.replace("'", r"'\''") + "'" for arg in args)
class CpuProperties(object):
def get_cpu_quota(self):
return int(load("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"))
def get_cpu_period(self):
return int(load("/sys/fs/cgroup/cpu/cpu.cfs_period_us"))
def get_cpus(self):
try:
cfs_quota_us = self.get_cpu_quota()
cfs_period_us = self.get_cpu_period()
if cfs_quota_us > 0 and cfs_period_us > 0:
return int(math.ceil(cfs_quota_us / cfs_period_us))
except:
pass
return multiprocessing.cpu_count()
def cpu_count(output=None):
try:
env_cpu_count = os.getenv("CONAN_CPU_COUNT", None)
if env_cpu_count is not None and not env_cpu_count.isdigit():
raise ConanException("Invalid CONAN_CPU_COUNT value '%s', "
"please specify a positive integer" % env_cpu_count)
if env_cpu_count:
return int(env_cpu_count)
else:
return CpuProperties().get_cpus()
except NotImplementedError:
output.warn("multiprocessing.cpu_count() not implemented. Defaulting to 1 cpu")
return 1 # Safe guess
def detected_os():
if OSInfo().is_macos:
return "Macos"
if OSInfo().is_windows:
return "Windows"
return platform.system()
def detected_architecture():
# FIXME: Very weak check but not very common to run conan in other architectures
machine = platform.machine()
os_info = OSInfo()
arch = None
if os_info.is_solaris:
arch = OSInfo.get_solaris_architecture()
elif os_info.is_aix:
arch = OSInfo.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 "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 OSInfo.get_e2k_architecture()
return None
# DETECT OS, VERSION AND DISTRIBUTIONS
class OSInfo(object):
""" Usage:
(os_info.is_linux) # True/False
(os_info.is_windows) # True/False
(os_info.is_macos) # True/False
(os_info.is_freebsd) # True/False
(os_info.is_solaris) # True/False
(os_info.linux_distro) # debian, ubuntu, fedora, centos...
(os_info.os_version) # 5.1
(os_info.os_version_name) # Windows 7, El Capitan
if os_info.os_version > "10.1":
pass
if os_info.os_version == "10.1.0":
pass
"""
def __init__(self):
system = platform.system()
self.os_version = None
self.os_version_name = None
self.is_linux = system == "Linux"
self.linux_distro = None
self.is_msys = system.startswith("MING") or system.startswith("MSYS_NT")
self.is_cygwin = system.startswith("CYGWIN_NT")
self.is_windows = system == "Windows" or self.is_msys or self.is_cygwin
self.is_macos = system == "Darwin"
self.is_freebsd = system == "FreeBSD"
self.is_solaris = system == "SunOS"
self.is_aix = system == "AIX"
self.is_posix = os.pathsep == ':'
if self.is_linux:
self._get_linux_distro_info()
elif self.is_windows:
self.os_version = self.get_win_os_version()
self.os_version_name = self.get_win_version_name(self.os_version)
elif self.is_macos:
self.os_version = Version(platform.mac_ver()[0])
self.os_version_name = self.get_osx_version_name(self.os_version)
elif self.is_freebsd:
self.os_version = self.get_freebsd_version()
self.os_version_name = "FreeBSD %s" % self.os_version
elif self.is_solaris:
self.os_version = Version(platform.release())
self.os_version_name = self.get_solaris_version_name(self.os_version)
elif self.is_aix:
self.os_version = self.get_aix_version()
self.os_version_name = "AIX %s" % self.os_version.minor(fill=False)
def _get_linux_distro_info(self):
import distro
self.linux_distro = distro.id()
self.os_version = Version(distro.version())
version_name = distro.codename()
self.os_version_name = version_name if version_name != "n/a" else ""
if not self.os_version_name and self.linux_distro == "debian":
self.os_version_name = self.get_debian_version_name(self.os_version)
@property
def with_apt(self):
if not self.is_linux:
return False
# https://github.com/conan-io/conan/issues/8737 zypper-aptitude can fake it
if "opensuse" in self.linux_distro or "sles" in self.linux_distro:
return False
apt_location = which('apt-get')
if apt_location:
# Check if we actually have the official apt package.
try:
output = check_output_runner([apt_location, 'moo'])
except CalledProcessErrorWithStderr:
return False
else:
# Yes, we have mooed today. :-) MOOOOOOOO.
return True
else:
return False
@property
def with_yum(self):
return self.is_linux and self.linux_distro in ("pidora", "fedora", "scientific", "centos",
"redhat", "rhel", "xenserver", "amazon",
"oracle", "amzn", "almalinux", "rocky")
@property
def with_dnf(self):
return self.is_linux and self.linux_distro == "fedora" and which('dnf')
@property
def with_pacman(self):
if self.is_linux:
return self.linux_distro in ["arch", "manjaro"]
elif self.is_windows and which('uname.exe'):
uname = check_output_runner(['uname.exe', '-s'])
return uname.startswith('MSYS_NT') and which('pacman.exe')
return False
@property
def with_zypper(self):
if not self.is_linux:
return False
if "opensuse" in self.linux_distro or "sles" in self.linux_distro:
return True
return False
@staticmethod
def get_win_os_version():
"""
Get's the OS major and minor versions. Returns a tuple of
(OS_MAJOR, OS_MINOR).
"""
import ctypes
class _OSVERSIONINFOEXW(ctypes.Structure):
_fields_ = [('dwOSVersionInfoSize', ctypes.c_ulong),
('dwMajorVersion', ctypes.c_ulong),
('dwMinorVersion', ctypes.c_ulong),
('dwBuildNumber', ctypes.c_ulong),
('dwPlatformId', ctypes.c_ulong),
('szCSDVersion', ctypes.c_wchar * 128),
('wServicePackMajor', ctypes.c_ushort),
('wServicePackMinor', ctypes.c_ushort),
('wSuiteMask', ctypes.c_ushort),
('wProductType', ctypes.c_byte),
('wReserved', ctypes.c_byte)]
os_version = _OSVERSIONINFOEXW()
os_version.dwOSVersionInfoSize = ctypes.sizeof(os_version)
if not hasattr(ctypes, "windll"):
return None
retcode = ctypes.windll.Ntdll.RtlGetVersion(ctypes.byref(os_version))
if retcode != 0:
return None
return Version("%d.%d" % (os_version.dwMajorVersion, os_version.dwMinorVersion))
@staticmethod
def get_debian_version_name(version):
if not version:
return None
elif version.major() == "8.Y.Z":
return "jessie"
elif version.major() == "7.Y.Z":
return "wheezy"
elif version.major() == "6.Y.Z":
return "squeeze"
elif version.major() == "5.Y.Z":
return "lenny"
elif version.major() == "4.Y.Z":
return "etch"
elif version.minor() == "3.1.Z":
return "sarge"
elif version.minor() == "3.0.Z":
return "woody"
@staticmethod
def get_win_version_name(version):
if not version:
return None
elif version.major() == "5.Y.Z":
return "Windows XP"
elif version.minor() == "6.0.Z":
return "Windows Vista"
elif version.minor() == "6.1.Z":
return "Windows 7"
elif version.minor() == "6.2.Z":
return "Windows 8"
elif version.minor() == "6.3.Z":
return "Windows 8.1"
elif version.minor() == "10.0.Z":
return "Windows 10"
@staticmethod
def get_osx_version_name(version):
if not version:
return None
elif version.minor() == "10.13.Z":
return "High Sierra"
elif version.minor() == "10.12.Z":
return "Sierra"
elif version.minor() == "10.11.Z":
return "El Capitan"
elif version.minor() == "10.10.Z":
return "Yosemite"
elif version.minor() == "10.9.Z":
return "Mavericks"
elif version.minor() == "10.8.Z":
return "Mountain Lion"
elif version.minor() == "10.7.Z":
return "Lion"
elif version.minor() == "10.6.Z":
return "Snow Leopard"
elif version.minor() == "10.5.Z":
return "Leopard"
elif version.minor() == "10.4.Z":
return "Tiger"
elif version.minor() == "10.3.Z":
return "Panther"
elif version.minor() == "10.2.Z":
return "Jaguar"
elif version.minor() == "10.1.Z":
return "Puma"
elif version.minor() == "10.0.Z":
return "Cheetha"
@staticmethod
def get_aix_architecture():
processor = platform.processor()
if "powerpc" in processor:
kernel_bitness = OSInfo().get_aix_conf("KERNEL_BITMODE")
if kernel_bitness:
return "ppc64" if kernel_bitness == "64" else "ppc32"
elif "rs6000" in processor:
return "ppc32"
@staticmethod
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"
@staticmethod
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())
@staticmethod
def get_freebsd_version():
return platform.release().split("-")[0]
@staticmethod
def get_solaris_version_name(version):
if not version:
return None
elif version.minor() == "5.10":
return "Solaris 10"
elif version.minor() == "5.11":
return "Solaris 11"
@staticmethod
def get_aix_version():
try:
ret = check_output_runner("oslevel").strip()
return Version(ret)
except Exception:
return Version("%s.%s" % (platform.version(), platform.release()))
@staticmethod
def bash_path():
if os.getenv("CONAN_BASH_PATH"):
return os.getenv("CONAN_BASH_PATH")
return which("bash")
@staticmethod
def uname(options=None):
options = " %s" % options if options else ""
if not OSInfo().is_windows:
raise ConanException("Command only for Windows operating system")
custom_bash_path = OSInfo.bash_path()
if not custom_bash_path:
raise ConanException("bash is not in the path")
command = '"%s" -c "uname%s"' % (custom_bash_path, options)
try:
# the uname executable is many times located in the same folder as bash.exe
with environment_append({"PATH": [os.path.dirname(custom_bash_path)]}):
ret = check_output_runner(command).strip().lower()
return ret
except Exception:
return None
@staticmethod
def get_aix_conf(options=None):
options = " %s" % options if options else ""
if not OSInfo().is_aix:
raise ConanException("Command only for AIX operating system")
try:
ret = check_output_runner("getconf%s" % options).strip()
return ret
except Exception:
return None
@staticmethod
def detect_windows_subsystem():
from conans.client.tools.win import CYGWIN, MSYS2, MSYS, WSL
if OSInfo().is_linux:
try:
# https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
with open("/proc/sys/kernel/osrelease") as f:
return WSL if f.read().endswith("Microsoft") else None
except IOError:
return None
try:
output = OSInfo.uname()
except ConanException:
return None
if not output:
return None
if "cygwin" in output:
return CYGWIN
elif "msys" in output or "mingw" in output:
version = OSInfo.uname("-r").split('.')
if version and version[0].isdigit():
major = int(version[0])
if major == 1:
return MSYS
elif major >= 2:
return MSYS2
return None
elif "linux" in output:
return WSL
else:
return None
def cross_building(conanfile=None, self_os=None, self_arch=None, skip_x64_x86=False, settings=None):
# Handle input arguments (backwards compatibility with 'settings' as first argument)
# TODO: This can be promoted to a decorator pattern for tools if we adopt 'conanfile' as the
# first argument for all of them.
if conanfile and settings:
raise ConanException("Do not set both arguments, 'conanfile' and 'settings',"
" to call cross_building function")
from conans.model.conan_file import ConanFile
if conanfile and not isinstance(conanfile, ConanFile):
return cross_building(settings=conanfile, self_os=self_os, self_arch=self_arch,
skip_x64_x86=skip_x64_x86)
if settings:
warnings.warn("Argument 'settings' has been deprecated, use 'conanfile' instead")
if conanfile:
ret = get_cross_building_settings(conanfile, self_os, self_arch)
else:
# TODO: If Conan is using 'profile_build' here we don't have any information about it,
# we are falling back to the old behavior (which is probably wrong here)
conanfile = namedtuple('_ConanFile', ['settings'])(settings)
ret = get_cross_building_settings(conanfile, self_os, self_arch)
build_os, build_arch, host_os, host_arch = ret
if skip_x64_x86 and host_os is not None and (build_os == host_os) and \
host_arch is not None and ((build_arch == "x86_64") and (host_arch == "x86") or
(build_arch == "sparcv9") and (host_arch == "sparc") or
(build_arch == "ppc64") and (host_arch == "ppc32")):
return False
if host_os is not None and (build_os != host_os):
return True
if host_arch is not None and (build_arch != host_arch):
return True
return False
def get_cross_building_settings(conanfile, self_os=None, self_arch=None):
os_build, arch_build = get_build_os_arch(conanfile)
if not hasattr(conanfile, 'settings_build'):
# Let it override from outside only if no 'profile_build' is used
os_build = self_os or os_build or detected_os()
arch_build = self_arch or arch_build or detected_architecture()
os_host = conanfile.settings.get_safe("os")
arch_host = conanfile.settings.get_safe("arch")
return os_build, arch_build, os_host, arch_host
def get_gnu_triplet(os_, arch, compiler=None):
"""
Returns string with <machine>-<vendor>-<op_system> triplet (<vendor> can be omitted in practice)
:param os_: os to be used to create the triplet
:param arch: arch to be used to create the triplet
:param compiler: compiler used to create the triplet (only needed fo windows)
"""
if os_ == "Windows" and compiler is None:
raise ConanException("'compiler' parameter for 'get_gnu_triplet()' is not specified and "
"needed for os=Windows")
# Calculate the arch
machine = {"x86": "i686" if os_ != "Linux" else "x86",
"x86_64": "x86_64",
"armv8": "aarch64",
"armv8_32": "aarch64", # https://wiki.linaro.org/Platform/arm64-ilp32
"armv8.3": "aarch64",
"asm.js": "asmjs",
"wasm": "wasm32",
}.get(arch, None)
if not machine:
# https://wiki.debian.org/Multiarch/Tuples
if os_ == "AIX":
if "ppc32" in arch:
machine = "rs6000"
elif "ppc64" in arch:
machine = "powerpc"
elif "arm" in arch:
machine = "arm"
elif "ppc32be" in arch:
machine = "powerpcbe"
elif "ppc64le" in arch:
machine = "powerpc64le"
elif "ppc64" in arch:
machine = "powerpc64"
elif "ppc32" in arch:
machine = "powerpc"
elif "mips64" in arch:
machine = "mips64"
elif "mips" in arch:
machine = "mips"
elif "sparcv9" in arch:
machine = "sparc64"
elif "sparc" in arch:
machine = "sparc"
elif "s390x" in arch:
machine = "s390x-ibm"
elif "s390" in arch:
machine = "s390-ibm"
elif "sh4" in arch:
machine = "sh4"
elif "e2k" in arch:
# https://lists.gnu.org/archive/html/config-patches/2015-03/msg00000.html
machine = "e2k-unknown"
if machine is None:
raise ConanException("Unknown '%s' machine, Conan doesn't know how to "
"translate it to the GNU triplet, please report at "
" https://github.com/conan-io/conan/issues" % arch)
# Calculate the OS
if compiler == "gcc":
windows_op = "w64-mingw32"
elif compiler == "Visual Studio":
windows_op = "windows-msvc"
else:
windows_op = "windows"
op_system = {"Windows": windows_op,
"Linux": "linux-gnu",
"Darwin": "apple-darwin",
"Android": "linux-android",
"Macos": "apple-darwin",
"iOS": "apple-ios",
"watchOS": "apple-watchos",
"tvOS": "apple-tvos",
# NOTE: it technically must be "asmjs-unknown-emscripten" or
# "wasm32-unknown-emscripten", but it's not recognized by old config.sub versions
"Emscripten": "local-emscripten",
"AIX": "ibm-aix",
"Neutrino": "nto-qnx"}.get(os_, os_.lower())
if os_ in ("Linux", "Android"):
if "arm" in arch and "armv8" not in arch:
op_system += "eabi"
if (arch == "armv5hf" or arch == "armv7hf") and os_ == "Linux":
op_system += "hf"
if arch == "armv8_32" and os_ == "Linux":
op_system += "_ilp32" # https://wiki.linaro.org/Platform/arm64-ilp32
return "%s-%s" % (machine, op_system)
def get_build_os_arch(conanfile):
""" Returns the value for the 'os' and 'arch' settings for the build context """
if hasattr(conanfile, 'settings_build'):
return conanfile.settings_build.get_safe('os'), conanfile.settings_build.get_safe('arch')
else:
return conanfile.settings.get_safe('os_build'), conanfile.settings.get_safe('arch_build')
def get_target_os_arch(conanfile):
""" Returns the value for the 'os' and 'arch' settings for the target context """
if hasattr(conanfile, 'settings_target'):
settings_target = conanfile.settings_target
if settings_target is not None:
return settings_target.get_safe('os'), settings_target.get_safe('arch')
return None, None
else:
return conanfile.settings.get_safe('os_target'), conanfile.settings.get_safe('arch_target')