envconfig.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. # Copyright 2012-2016 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. import os, subprocess
  12. import typing as T
  13. from . import mesonlib
  14. from .mesonlib import EnvironmentException, MachineChoice, PerMachine, split_args
  15. from . import mlog
  16. _T = T.TypeVar('_T')
  17. # These classes contains all the data pulled from configuration files (native
  18. # and cross file currently), and also assists with the reading environment
  19. # variables.
  20. #
  21. # At this time there isn't an ironclad difference between this an other sources
  22. # of state like `coredata`. But one rough guide is much what is in `coredata` is
  23. # the *output* of the configuration process: the final decisions after tests.
  24. # This, on the other hand has *inputs*. The config files are parsed, but
  25. # otherwise minimally transformed. When more complex fallbacks (environment
  26. # detection) exist, they are defined elsewhere as functions that construct
  27. # instances of these classes.
  28. ###lqg
  29. known_cpu_families = (
  30. 'allarch',
  31. 'aarch64',
  32. 'alpha',
  33. 'arc',
  34. 'arm',
  35. 'avr',
  36. 'c2000',
  37. 'dspic',
  38. 'e2k',
  39. 'ia64',
  40. 'm68k',
  41. 'microblaze',
  42. 'mips',
  43. 'mips64',
  44. 'parisc',
  45. 'pic24',
  46. 'ppc',
  47. 'ppc64',
  48. 'riscv32',
  49. 'riscv64',
  50. 'csky',
  51. 'rl78',
  52. 'rx',
  53. 's390',
  54. 's390x',
  55. 'sh4',
  56. 'sparc',
  57. 'sparc64',
  58. 'wasm32',
  59. 'wasm64',
  60. 'x86',
  61. 'x86_64',
  62. )
  63. # It would feel more natural to call this "64_BIT_CPU_FAMILES", but
  64. # python identifiers cannot start with numbers
  65. CPU_FAMILES_64_BIT = [
  66. 'aarch64',
  67. 'alpha',
  68. 'ia64',
  69. 'mips64',
  70. 'ppc64',
  71. 'riscv64',
  72. 's390x',
  73. 'sparc64',
  74. 'wasm64',
  75. 'x86_64',
  76. ]
  77. def get_env_var_pair(for_machine: MachineChoice,
  78. is_cross: bool,
  79. var_name: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
  80. """
  81. Returns the exact env var and the value.
  82. """
  83. candidates = PerMachine(
  84. # The prefixed build version takes priority, but if we are native
  85. # compiling we fall back on the unprefixed host version. This
  86. # allows native builds to never need to worry about the 'BUILD_*'
  87. # ones.
  88. ([var_name + '_FOR_BUILD'] if is_cross else [var_name]),
  89. # Always just the unprefixed host verions
  90. [var_name]
  91. )[for_machine]
  92. for var in candidates:
  93. value = os.environ.get(var)
  94. if value is not None:
  95. break
  96. else:
  97. formatted = ', '.join(['{!r}'.format(var) for var in candidates])
  98. mlog.debug('None of {} are defined in the environment, not changing global flags.'.format(formatted))
  99. return None
  100. mlog.log('Using {!r} from environment with value: {!r}'.format(var, value))
  101. return var, value
  102. def get_env_var(for_machine: MachineChoice,
  103. is_cross: bool,
  104. var_name: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
  105. ret = get_env_var_pair(for_machine, is_cross, var_name)
  106. if ret is None:
  107. return None
  108. else:
  109. var, value = ret
  110. return value
  111. class Properties:
  112. def __init__(
  113. self,
  114. properties: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None,
  115. ):
  116. self.properties = properties or {} # type: T.Dict[str, T.Union[str, T.List[str]]]
  117. def has_stdlib(self, language: str) -> bool:
  118. return language + '_stdlib' in self.properties
  119. # Some of get_stdlib, get_root, get_sys_root are wider than is actually
  120. # true, but without heterogenious dict annotations it's not practical to
  121. # narrow them
  122. def get_stdlib(self, language: str) -> T.Union[str, T.List[str]]:
  123. return self.properties[language + '_stdlib']
  124. def get_root(self) -> T.Optional[T.Union[str, T.List[str]]]:
  125. return self.properties.get('root', None)
  126. def get_sys_root(self) -> T.Optional[T.Union[str, T.List[str]]]:
  127. return self.properties.get('sys_root', None)
  128. def get_pkg_config_libdir(self) -> T.Optional[T.List[str]]:
  129. p = self.properties.get('pkg_config_libdir', None)
  130. if p is None:
  131. return p
  132. return mesonlib.listify(p)
  133. def __eq__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
  134. if isinstance(other, type(self)):
  135. return self.properties == other.properties
  136. return NotImplemented
  137. # TODO consider removing so Properties is less freeform
  138. def __getitem__(self, key: str) -> T.Any:
  139. return self.properties[key]
  140. # TODO consider removing so Properties is less freeform
  141. def __contains__(self, item: T.Any) -> bool:
  142. return item in self.properties
  143. # TODO consider removing, for same reasons as above
  144. def get(self, key: str, default: T.Any = None) -> T.Any:
  145. return self.properties.get(key, default)
  146. class MachineInfo:
  147. def __init__(self, system: str, cpu_family: str, cpu: str, endian: str):
  148. self.system = system
  149. self.cpu_family = cpu_family
  150. self.cpu = cpu
  151. self.endian = endian
  152. self.is_64_bit = cpu_family in CPU_FAMILES_64_BIT # type: bool
  153. def __eq__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
  154. if self.__class__ is not other.__class__:
  155. return NotImplemented
  156. return \
  157. self.system == other.system and \
  158. self.cpu_family == other.cpu_family and \
  159. self.cpu == other.cpu and \
  160. self.endian == other.endian
  161. def __ne__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
  162. if self.__class__ is not other.__class__:
  163. return NotImplemented
  164. return not self.__eq__(other)
  165. def __repr__(self) -> str:
  166. return '<MachineInfo: {} {} ({})>'.format(self.system, self.cpu_family, self.cpu)
  167. @classmethod
  168. def from_literal(cls, literal: T.Dict[str, str]) -> 'MachineInfo':
  169. minimum_literal = {'cpu', 'cpu_family', 'endian', 'system'}
  170. if set(literal) < minimum_literal:
  171. raise EnvironmentException(
  172. 'Machine info is currently {}\n'.format(literal) +
  173. 'but is missing {}.'.format(minimum_literal - set(literal)))
  174. cpu_family = literal['cpu_family']
  175. if cpu_family not in known_cpu_families:
  176. raise EnvironmentException('Unknown CPU family {}, see https://wiki.yoctoproject.org/wiki/Meson/UnknownCPU for directions.'.format(cpu_family))
  177. endian = literal['endian']
  178. if endian not in ('little', 'big'):
  179. mlog.warning('Unknown endian {}'.format(endian))
  180. return cls(literal['system'], cpu_family, literal['cpu'], endian)
  181. def is_windows(self) -> bool:
  182. """
  183. Machine is windows?
  184. """
  185. return self.system == 'windows' or 'mingw' in self.system
  186. def is_cygwin(self) -> bool:
  187. """
  188. Machine is cygwin?
  189. """
  190. return self.system.startswith('cygwin')
  191. def is_linux(self) -> bool:
  192. """
  193. Machine is linux?
  194. """
  195. return self.system == 'linux'
  196. def is_darwin(self) -> bool:
  197. """
  198. Machine is Darwin (iOS/tvOS/OS X)?
  199. """
  200. return self.system in {'darwin', 'ios', 'tvos'}
  201. def is_android(self) -> bool:
  202. """
  203. Machine is Android?
  204. """
  205. return self.system == 'android'
  206. def is_haiku(self) -> bool:
  207. """
  208. Machine is Haiku?
  209. """
  210. return self.system == 'haiku'
  211. def is_netbsd(self) -> bool:
  212. """
  213. Machine is NetBSD?
  214. """
  215. return self.system == 'netbsd'
  216. def is_openbsd(self) -> bool:
  217. """
  218. Machine is OpenBSD?
  219. """
  220. return self.system == 'openbsd'
  221. def is_dragonflybsd(self) -> bool:
  222. """Machine is DragonflyBSD?"""
  223. return self.system == 'dragonfly'
  224. def is_freebsd(self) -> bool:
  225. """Machine is FreeBSD?"""
  226. return self.system == 'freebsd'
  227. def is_sunos(self) -> bool:
  228. """Machine is illumos or Solaris?"""
  229. return self.system == 'sunos'
  230. def is_hurd(self) -> bool:
  231. """
  232. Machine is GNU/Hurd?
  233. """
  234. return self.system == 'gnu'
  235. def is_irix(self) -> bool:
  236. """Machine is IRIX?"""
  237. return self.system.startswith('irix')
  238. # Various prefixes and suffixes for import libraries, shared libraries,
  239. # static libraries, and executables.
  240. # Versioning is added to these names in the backends as-needed.
  241. def get_exe_suffix(self) -> str:
  242. if self.is_windows() or self.is_cygwin():
  243. return 'exe'
  244. else:
  245. return ''
  246. def get_object_suffix(self) -> str:
  247. if self.is_windows():
  248. return 'obj'
  249. else:
  250. return 'o'
  251. def libdir_layout_is_win(self) -> bool:
  252. return self.is_windows() or self.is_cygwin()
  253. class BinaryTable:
  254. def __init__(
  255. self,
  256. binaries: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None,
  257. ):
  258. self.binaries = binaries or {} # type: T.Dict[str, T.Union[str, T.List[str]]]
  259. for name, command in self.binaries.items():
  260. if not isinstance(command, (list, str)):
  261. # TODO generalize message
  262. raise mesonlib.MesonException(
  263. 'Invalid type {!r} for binary {!r} in cross file'
  264. ''.format(command, name))
  265. # Map from language identifiers to environment variables.
  266. evarMap = {
  267. # Compilers
  268. 'c': 'CC',
  269. 'cpp': 'CXX',
  270. 'cs': 'CSC',
  271. 'd': 'DC',
  272. 'fortran': 'FC',
  273. 'objc': 'OBJC',
  274. 'objcpp': 'OBJCXX',
  275. 'rust': 'RUSTC',
  276. 'vala': 'VALAC',
  277. # Linkers
  278. 'c_ld': 'CC_LD',
  279. 'cpp_ld': 'CXX_LD',
  280. 'd_ld': 'DC_LD',
  281. 'fortran_ld': 'FC_LD',
  282. 'objc_ld': 'OBJC_LD',
  283. 'objcpp_ld': 'OBJCXX_LD',
  284. 'rust_ld': 'RUSTC_LD',
  285. # Binutils
  286. 'strip': 'STRIP',
  287. 'ar': 'AR',
  288. 'windres': 'WINDRES',
  289. # Other tools
  290. 'cmake': 'CMAKE',
  291. 'qmake': 'QMAKE',
  292. 'pkgconfig': 'PKG_CONFIG',
  293. } # type: T.Dict[str, str]
  294. # Deprecated environment variables mapped from the new variable to the old one
  295. # Deprecated in 0.54.0
  296. DEPRECATION_MAP = {
  297. 'DC_LD': 'D_LD',
  298. 'FC_LD': 'F_LD',
  299. 'RUSTC_LD': 'RUST_LD',
  300. 'OBJCXX_LD': 'OBJCPP_LD',
  301. } # type: T.Dict[str, str]
  302. @staticmethod
  303. def detect_ccache() -> T.List[str]:
  304. try:
  305. subprocess.check_call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  306. except (OSError, subprocess.CalledProcessError):
  307. return []
  308. return ['ccache']
  309. @classmethod
  310. def parse_entry(cls, entry: T.Union[str, T.List[str]]) -> T.Tuple[T.List[str], T.List[str]]:
  311. compiler = mesonlib.stringlistify(entry)
  312. # Ensure ccache exists and remove it if it doesn't
  313. if compiler[0] == 'ccache':
  314. compiler = compiler[1:]
  315. ccache = cls.detect_ccache()
  316. else:
  317. ccache = []
  318. # Return value has to be a list of compiler 'choices'
  319. return compiler, ccache
  320. def lookup_entry(self,
  321. for_machine: MachineChoice,
  322. is_cross: bool,
  323. name: str) -> T.Optional[T.List[str]]:
  324. """Lookup binary in cross/native file and fallback to environment.
  325. Returns command with args as list if found, Returns `None` if nothing is
  326. found.
  327. """
  328. # Try explicit map, don't fall back on env var
  329. # Try explict map, then env vars
  330. for _ in [()]: # a trick to get `break`
  331. raw_command = self.binaries.get(name)
  332. if raw_command is not None:
  333. command = mesonlib.stringlistify(raw_command)
  334. break # found
  335. evar = self.evarMap.get(name)
  336. if evar is not None:
  337. raw_command = get_env_var(for_machine, is_cross, evar)
  338. if raw_command is None:
  339. deprecated = self.DEPRECATION_MAP.get(evar)
  340. if deprecated is not None:
  341. raw_command = get_env_var(for_machine, is_cross, deprecated)
  342. if raw_command is not None:
  343. mlog.deprecation(
  344. 'The', deprecated, 'environment variable is deprecated in favor of',
  345. evar, once=True)
  346. if raw_command is not None:
  347. command = split_args(raw_command)
  348. break # found
  349. command = None
  350. # Do not return empty or blank string entries
  351. if command is not None and (len(command) == 0 or len(command[0].strip()) == 0):
  352. command = None
  353. return command
  354. class Directories:
  355. """Data class that holds information about directories for native and cross
  356. builds.
  357. """
  358. def __init__(self, bindir: T.Optional[str] = None, datadir: T.Optional[str] = None,
  359. includedir: T.Optional[str] = None, infodir: T.Optional[str] = None,
  360. libdir: T.Optional[str] = None, libexecdir: T.Optional[str] = None,
  361. localedir: T.Optional[str] = None, localstatedir: T.Optional[str] = None,
  362. mandir: T.Optional[str] = None, prefix: T.Optional[str] = None,
  363. sbindir: T.Optional[str] = None, sharedstatedir: T.Optional[str] = None,
  364. sysconfdir: T.Optional[str] = None):
  365. self.bindir = bindir
  366. self.datadir = datadir
  367. self.includedir = includedir
  368. self.infodir = infodir
  369. self.libdir = libdir
  370. self.libexecdir = libexecdir
  371. self.localedir = localedir
  372. self.localstatedir = localstatedir
  373. self.mandir = mandir
  374. self.prefix = prefix
  375. self.sbindir = sbindir
  376. self.sharedstatedir = sharedstatedir
  377. self.sysconfdir = sysconfdir
  378. def __contains__(self, key: str) -> bool:
  379. return hasattr(self, key)
  380. def __getitem__(self, key: str) -> T.Optional[str]:
  381. # Mypy can't figure out what to do with getattr here, so we'll case for it
  382. return T.cast(T.Optional[str], getattr(self, key))
  383. def __setitem__(self, key: str, value: T.Optional[str]) -> None:
  384. setattr(self, key, value)
  385. def __iter__(self) -> T.Iterator[T.Tuple[str, str]]:
  386. return iter(self.__dict__.items())