1# Copyright 2012-2016 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import subprocess 16import typing as T 17from enum import Enum 18 19from . import mesonlib 20from .mesonlib import EnvironmentException, HoldableObject 21from . import mlog 22from pathlib import Path 23 24 25# These classes contains all the data pulled from configuration files (native 26# and cross file currently), and also assists with the reading environment 27# variables. 28# 29# At this time there isn't an ironclad difference between this an other sources 30# of state like `coredata`. But one rough guide is much what is in `coredata` is 31# the *output* of the configuration process: the final decisions after tests. 32# This, on the other hand has *inputs*. The config files are parsed, but 33# otherwise minimally transformed. When more complex fallbacks (environment 34# detection) exist, they are defined elsewhere as functions that construct 35# instances of these classes. 36 37 38known_cpu_families = ( 39 'aarch64', 40 'alpha', 41 'arc', 42 'arm', 43 'avr', 44 'c2000', 45 'csky', 46 'dspic', 47 'e2k', 48 'ft32', 49 'ia64', 50 'loongarch64', 51 'm68k', 52 'microblaze', 53 'mips', 54 'mips64', 55 'parisc', 56 'pic24', 57 'ppc', 58 'ppc64', 59 'riscv32', 60 'riscv64', 61 'rl78', 62 'rx', 63 's390', 64 's390x', 65 'sh4', 66 'sparc', 67 'sparc64', 68 'wasm32', 69 'wasm64', 70 'x86', 71 'x86_64', 72) 73 74# It would feel more natural to call this "64_BIT_CPU_FAMILIES", but 75# python identifiers cannot start with numbers 76CPU_FAMILIES_64_BIT = [ 77 'aarch64', 78 'alpha', 79 'ia64', 80 'loongarch64', 81 'mips64', 82 'ppc64', 83 'riscv64', 84 's390x', 85 'sparc64', 86 'wasm64', 87 'x86_64', 88] 89 90# Map from language identifiers to environment variables. 91ENV_VAR_PROG_MAP: T.Mapping[str, str] = { 92 # Compilers 93 'c': 'CC', 94 'cpp': 'CXX', 95 'cs': 'CSC', 96 'd': 'DC', 97 'fortran': 'FC', 98 'objc': 'OBJC', 99 'objcpp': 'OBJCXX', 100 'rust': 'RUSTC', 101 'vala': 'VALAC', 102 103 # Linkers 104 'c_ld': 'CC_LD', 105 'cpp_ld': 'CXX_LD', 106 'd_ld': 'DC_LD', 107 'fortran_ld': 'FC_LD', 108 'objc_ld': 'OBJC_LD', 109 'objcpp_ld': 'OBJCXX_LD', 110 'rust_ld': 'RUSTC_LD', 111 112 # Binutils 113 'strip': 'STRIP', 114 'ar': 'AR', 115 'windres': 'WINDRES', 116 117 # Other tools 118 'cmake': 'CMAKE', 119 'qmake': 'QMAKE', 120 'pkgconfig': 'PKG_CONFIG', 121 'make': 'MAKE', 122} 123 124# Deprecated environment variables mapped from the new variable to the old one 125# Deprecated in 0.54.0 126DEPRECATED_ENV_PROG_MAP: T.Mapping[str, str] = { 127 'd_ld': 'D_LD', 128 'fortran_ld': 'F_LD', 129 'rust_ld': 'RUST_LD', 130 'objcpp_ld': 'OBJCPP_LD', 131} 132 133class CMakeSkipCompilerTest(Enum): 134 ALWAYS = 'always' 135 NEVER = 'never' 136 DEP_ONLY = 'dep_only' 137 138class Properties: 139 def __init__( 140 self, 141 properties: T.Optional[T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]] = None, 142 ): 143 self.properties = properties or {} # type: T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]] 144 145 def has_stdlib(self, language: str) -> bool: 146 return language + '_stdlib' in self.properties 147 148 # Some of get_stdlib, get_root, get_sys_root are wider than is actually 149 # true, but without heterogenious dict annotations it's not practical to 150 # narrow them 151 def get_stdlib(self, language: str) -> T.Union[str, T.List[str]]: 152 stdlib = self.properties[language + '_stdlib'] 153 if isinstance(stdlib, str): 154 return stdlib 155 assert isinstance(stdlib, list) 156 for i in stdlib: 157 assert isinstance(i, str) 158 return stdlib 159 160 def get_root(self) -> T.Optional[str]: 161 root = self.properties.get('root', None) 162 assert root is None or isinstance(root, str) 163 return root 164 165 def get_sys_root(self) -> T.Optional[str]: 166 sys_root = self.properties.get('sys_root', None) 167 assert sys_root is None or isinstance(sys_root, str) 168 return sys_root 169 170 def get_pkg_config_libdir(self) -> T.Optional[T.List[str]]: 171 p = self.properties.get('pkg_config_libdir', None) 172 if p is None: 173 return p 174 res = mesonlib.listify(p) 175 for i in res: 176 assert isinstance(i, str) 177 return res 178 179 def get_cmake_defaults(self) -> bool: 180 if 'cmake_defaults' not in self.properties: 181 return True 182 res = self.properties['cmake_defaults'] 183 assert isinstance(res, bool) 184 return res 185 186 def get_cmake_toolchain_file(self) -> T.Optional[Path]: 187 if 'cmake_toolchain_file' not in self.properties: 188 return None 189 raw = self.properties['cmake_toolchain_file'] 190 assert isinstance(raw, str) 191 cmake_toolchain_file = Path(raw) 192 if not cmake_toolchain_file.is_absolute(): 193 raise EnvironmentException(f'cmake_toolchain_file ({raw}) is not absolute') 194 return cmake_toolchain_file 195 196 def get_cmake_skip_compiler_test(self) -> CMakeSkipCompilerTest: 197 if 'cmake_skip_compiler_test' not in self.properties: 198 return CMakeSkipCompilerTest.DEP_ONLY 199 raw = self.properties['cmake_skip_compiler_test'] 200 assert isinstance(raw, str) 201 try: 202 return CMakeSkipCompilerTest(raw) 203 except ValueError: 204 raise EnvironmentException( 205 '"{}" is not a valid value for cmake_skip_compiler_test. Supported values are {}' 206 .format(raw, [e.value for e in CMakeSkipCompilerTest])) 207 208 def get_cmake_use_exe_wrapper(self) -> bool: 209 if 'cmake_use_exe_wrapper' not in self.properties: 210 return True 211 res = self.properties['cmake_use_exe_wrapper'] 212 assert isinstance(res, bool) 213 return res 214 215 def get_java_home(self) -> T.Optional[Path]: 216 value = T.cast(T.Optional[str], self.properties.get('java_home')) 217 return Path(value) if value else None 218 219 def __eq__(self, other: object) -> bool: 220 if isinstance(other, type(self)): 221 return self.properties == other.properties 222 return NotImplemented 223 224 # TODO consider removing so Properties is less freeform 225 def __getitem__(self, key: str) -> T.Optional[T.Union[str, bool, int, T.List[str]]]: 226 return self.properties[key] 227 228 # TODO consider removing so Properties is less freeform 229 def __contains__(self, item: T.Union[str, bool, int, T.List[str]]) -> bool: 230 return item in self.properties 231 232 # TODO consider removing, for same reasons as above 233 def get(self, key: str, default: T.Optional[T.Union[str, bool, int, T.List[str]]] = None) -> T.Optional[T.Union[str, bool, int, T.List[str]]]: 234 return self.properties.get(key, default) 235 236class MachineInfo(HoldableObject): 237 def __init__(self, system: str, cpu_family: str, cpu: str, endian: str): 238 self.system = system 239 self.cpu_family = cpu_family 240 self.cpu = cpu 241 self.endian = endian 242 self.is_64_bit = cpu_family in CPU_FAMILIES_64_BIT # type: bool 243 244 def __eq__(self, other: object) -> bool: 245 if not isinstance(other, MachineInfo): 246 return NotImplemented 247 return \ 248 self.system == other.system and \ 249 self.cpu_family == other.cpu_family and \ 250 self.cpu == other.cpu and \ 251 self.endian == other.endian 252 253 def __ne__(self, other: object) -> bool: 254 if not isinstance(other, MachineInfo): 255 return NotImplemented 256 return not self.__eq__(other) 257 258 def __repr__(self) -> str: 259 return f'<MachineInfo: {self.system} {self.cpu_family} ({self.cpu})>' 260 261 @classmethod 262 def from_literal(cls, literal: T.Dict[str, str]) -> 'MachineInfo': 263 minimum_literal = {'cpu', 'cpu_family', 'endian', 'system'} 264 if set(literal) < minimum_literal: 265 raise EnvironmentException( 266 f'Machine info is currently {literal}\n' + 267 'but is missing {}.'.format(minimum_literal - set(literal))) 268 269 cpu_family = literal['cpu_family'] 270 if cpu_family not in known_cpu_families: 271 mlog.warning(f'Unknown CPU family {cpu_family}, please report this at https://github.com/mesonbuild/meson/issues/new') 272 273 endian = literal['endian'] 274 if endian not in ('little', 'big'): 275 mlog.warning(f'Unknown endian {endian}') 276 277 return cls(literal['system'], cpu_family, literal['cpu'], endian) 278 279 def is_windows(self) -> bool: 280 """ 281 Machine is windows? 282 """ 283 return self.system == 'windows' 284 285 def is_cygwin(self) -> bool: 286 """ 287 Machine is cygwin? 288 """ 289 return self.system == 'cygwin' 290 291 def is_linux(self) -> bool: 292 """ 293 Machine is linux? 294 """ 295 return self.system == 'linux' 296 297 def is_darwin(self) -> bool: 298 """ 299 Machine is Darwin (iOS/tvOS/OS X)? 300 """ 301 return self.system in {'darwin', 'ios', 'tvos'} 302 303 def is_android(self) -> bool: 304 """ 305 Machine is Android? 306 """ 307 return self.system == 'android' 308 309 def is_haiku(self) -> bool: 310 """ 311 Machine is Haiku? 312 """ 313 return self.system == 'haiku' 314 315 def is_netbsd(self) -> bool: 316 """ 317 Machine is NetBSD? 318 """ 319 return self.system == 'netbsd' 320 321 def is_openbsd(self) -> bool: 322 """ 323 Machine is OpenBSD? 324 """ 325 return self.system == 'openbsd' 326 327 def is_dragonflybsd(self) -> bool: 328 """Machine is DragonflyBSD?""" 329 return self.system == 'dragonfly' 330 331 def is_freebsd(self) -> bool: 332 """Machine is FreeBSD?""" 333 return self.system == 'freebsd' 334 335 def is_sunos(self) -> bool: 336 """Machine is illumos or Solaris?""" 337 return self.system == 'sunos' 338 339 def is_hurd(self) -> bool: 340 """ 341 Machine is GNU/Hurd? 342 """ 343 return self.system == 'gnu' 344 345 def is_irix(self) -> bool: 346 """Machine is IRIX?""" 347 return self.system.startswith('irix') 348 349 # Various prefixes and suffixes for import libraries, shared libraries, 350 # static libraries, and executables. 351 # Versioning is added to these names in the backends as-needed. 352 def get_exe_suffix(self) -> str: 353 if self.is_windows() or self.is_cygwin(): 354 return 'exe' 355 else: 356 return '' 357 358 def get_object_suffix(self) -> str: 359 if self.is_windows(): 360 return 'obj' 361 else: 362 return 'o' 363 364 def libdir_layout_is_win(self) -> bool: 365 return self.is_windows() or self.is_cygwin() 366 367class BinaryTable: 368 369 def __init__( 370 self, 371 binaries: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None, 372 ): 373 self.binaries: T.Dict[str, T.List[str]] = {} 374 if binaries: 375 for name, command in binaries.items(): 376 if not isinstance(command, (list, str)): 377 raise mesonlib.MesonException( 378 f'Invalid type {command!r} for entry {name!r} in cross file') 379 self.binaries[name] = mesonlib.listify(command) 380 381 @staticmethod 382 def detect_ccache() -> T.List[str]: 383 try: 384 subprocess.check_call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 385 except (OSError, subprocess.CalledProcessError): 386 return [] 387 return ['ccache'] 388 389 @classmethod 390 def parse_entry(cls, entry: T.Union[str, T.List[str]]) -> T.Tuple[T.List[str], T.List[str]]: 391 compiler = mesonlib.stringlistify(entry) 392 # Ensure ccache exists and remove it if it doesn't 393 if compiler[0] == 'ccache': 394 compiler = compiler[1:] 395 ccache = cls.detect_ccache() 396 else: 397 ccache = [] 398 # Return value has to be a list of compiler 'choices' 399 return compiler, ccache 400 401 def lookup_entry(self, name: str) -> T.Optional[T.List[str]]: 402 """Lookup binary in cross/native file and fallback to environment. 403 404 Returns command with args as list if found, Returns `None` if nothing is 405 found. 406 """ 407 command = self.binaries.get(name) 408 if not command: 409 return None 410 elif not command[0].strip(): 411 return None 412 return command 413 414class CMakeVariables: 415 def __init__(self, variables: T.Optional[T.Dict[str, T.Any]] = None) -> None: 416 variables = variables or {} 417 self.variables = {} # type: T.Dict[str, T.List[str]] 418 419 for key, value in variables.items(): 420 value = mesonlib.listify(value) 421 for i in value: 422 assert isinstance(i, str) 423 self.variables[key] = value 424 425 def get_variables(self) -> T.Dict[str, T.List[str]]: 426 return self.variables 427