dependencies.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. # Copyright 2013-2015 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. # This file contains the detection logic for external
  12. # dependencies. Mostly just uses pkg-config but also contains
  13. # custom logic for packages that don't provide them.
  14. # Currently one file, should probably be split into a
  15. # package before this gets too big.
  16. import re
  17. import platform
  18. import os, stat, glob, subprocess, shutil
  19. from coredata import MesonException
  20. import mlog
  21. import mesonlib
  22. class DependencyException(MesonException):
  23. def __init__(self, *args, **kwargs):
  24. MesonException.__init__(self, *args, **kwargs)
  25. class Dependency():
  26. def __init__(self):
  27. self.name = "null"
  28. self.is_found = False
  29. def get_compile_args(self):
  30. return []
  31. def get_link_args(self):
  32. return []
  33. def found(self):
  34. return self.is_found
  35. def get_sources(self):
  36. """Source files that need to be added to the target.
  37. As an example, gtest-all.cc when using GTest."""
  38. return []
  39. def get_name(self):
  40. return self.name
  41. def get_exe_args(self):
  42. return []
  43. def need_threads(self):
  44. return False
  45. class InternalDependency():
  46. def __init__(self, incdirs, libraries, sources):
  47. super().__init__()
  48. self.include_directories = incdirs
  49. self.libraries = libraries
  50. self.sources = sources
  51. class PkgConfigDependency(Dependency):
  52. pkgconfig_found = None
  53. def __init__(self, name, environment, kwargs):
  54. Dependency.__init__(self)
  55. self.required = kwargs.get('required', True)
  56. if 'native' in kwargs and environment.is_cross_build():
  57. want_cross = not kwargs['native']
  58. else:
  59. want_cross = environment.is_cross_build()
  60. self.name = name
  61. if PkgConfigDependency.pkgconfig_found is None:
  62. self.check_pkgconfig()
  63. self.is_found = False
  64. if not PkgConfigDependency.pkgconfig_found:
  65. if self.required:
  66. raise DependencyException('Pkg-config not found.')
  67. self.cargs = []
  68. self.libs = []
  69. return
  70. if environment.is_cross_build() and want_cross:
  71. if "pkgconfig" not in environment.cross_info:
  72. raise DependencyException('Pkg-config binary missing from cross file.')
  73. pkgbin = environment.cross_info['pkgconfig']
  74. self.type_string = 'Cross'
  75. else:
  76. pkgbin = 'pkg-config'
  77. self.type_string = 'Native'
  78. self.pkgbin = pkgbin
  79. p = subprocess.Popen([pkgbin, '--modversion', name],
  80. stdout=subprocess.PIPE,
  81. stderr=subprocess.PIPE)
  82. out = p.communicate()[0]
  83. if p.returncode != 0:
  84. if self.required:
  85. raise DependencyException('%s dependency %s not found.' % (self.type_string, name))
  86. self.modversion = 'none'
  87. self.cargs = []
  88. self.libs = []
  89. else:
  90. self.modversion = out.decode().strip()
  91. mlog.log('%s dependency' % self.type_string, mlog.bold(name), 'found:',
  92. mlog.green('YES'), self.modversion)
  93. self.version_requirement = kwargs.get('version', None)
  94. if self.version_requirement is None:
  95. self.is_found = True
  96. else:
  97. if not isinstance(self.version_requirement, str):
  98. raise DependencyException('Version argument must be string.')
  99. self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement)
  100. if not self.is_found and self.required:
  101. raise DependencyException(
  102. 'Invalid version of a dependency, needed %s %s found %s.' %
  103. (name, self.version_requirement, self.modversion))
  104. if not self.is_found:
  105. return
  106. p = subprocess.Popen([pkgbin, '--cflags', name], stdout=subprocess.PIPE,
  107. stderr=subprocess.PIPE)
  108. out = p.communicate()[0]
  109. if p.returncode != 0:
  110. raise RuntimeError('Could not generate cargs for %s.' % name)
  111. self.cargs = out.decode().split()
  112. p = subprocess.Popen([pkgbin, '--libs', name], stdout=subprocess.PIPE,
  113. stderr=subprocess.PIPE)
  114. out = p.communicate()[0]
  115. if p.returncode != 0:
  116. raise RuntimeError('Could not generate libs for %s.' % name)
  117. self.libs = []
  118. for lib in out.decode().split():
  119. if lib.endswith(".la"):
  120. shared_libname = self.extract_libtool_shlib(lib)
  121. shared_lib = os.path.join(os.path.dirname(lib), shared_libname)
  122. if not os.path.exists(shared_lib):
  123. shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname)
  124. if not os.path.exists(shared_lib):
  125. raise RuntimeError('Got a libtools specific "%s" dependencies'
  126. 'but we could not compute the actual shared'
  127. 'library path' % lib)
  128. lib = shared_lib
  129. self.libs.append(lib)
  130. def get_variable(self, variable_name):
  131. p = subprocess.Popen([self.pkgbin, '--variable=%s' % variable_name, self.name],
  132. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  133. out = p.communicate()[0]
  134. if p.returncode != 0:
  135. if self.required:
  136. raise DependencyException('%s dependency %s not found.' %
  137. (self.type_string, self.name))
  138. else:
  139. variable = out.decode().strip()
  140. mlog.debug('return of subprocess : %s' % variable)
  141. return variable
  142. def get_modversion(self):
  143. return self.modversion
  144. def get_compile_args(self):
  145. return self.cargs
  146. def get_link_args(self):
  147. return self.libs
  148. def check_pkgconfig(self):
  149. try:
  150. p = subprocess.Popen(['pkg-config', '--version'], stdout=subprocess.PIPE,
  151. stderr=subprocess.PIPE)
  152. out = p.communicate()[0]
  153. if p.returncode == 0:
  154. mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')),
  155. '(%s)' % out.decode().strip())
  156. PkgConfigDependency.pkgconfig_found = True
  157. return
  158. except Exception:
  159. pass
  160. PkgConfigDependency.pkgconfig_found = False
  161. mlog.log('Found Pkg-config:', mlog.red('NO'))
  162. def found(self):
  163. return self.is_found
  164. def extract_field(self, la_file, fieldname):
  165. for line in open(la_file):
  166. arr = line.strip().split('=')
  167. if arr[0] == fieldname:
  168. return arr[1][1:-1]
  169. return None
  170. def extract_dlname_field(self, la_file):
  171. return self.extract_field(la_file, 'dlname')
  172. def extract_libdir_field(self, la_file):
  173. return self.extract_field(la_file, 'libdir')
  174. def extract_libtool_shlib(self, la_file):
  175. '''
  176. Returns the path to the shared library
  177. corresponding to this .la file
  178. '''
  179. dlname = self.extract_dlname_field(la_file)
  180. if dlname is None:
  181. return None
  182. # Darwin uses absolute paths where possible; since the libtool files never
  183. # contain absolute paths, use the libdir field
  184. if mesonlib.is_osx():
  185. dlbasename = os.path.basename(dlname)
  186. libdir = self.extract_libdir_field(la_file)
  187. if libdir is None:
  188. return dlbasename
  189. return os.path.join(libdir, dlbasename)
  190. # From the comments in extract_libtool(), older libtools had
  191. # a path rather than the raw dlname
  192. return os.path.basename(dlname)
  193. class WxDependency(Dependency):
  194. wx_found = None
  195. def __init__(self, environment, kwargs):
  196. Dependency.__init__(self)
  197. if WxDependency.wx_found is None:
  198. self.check_wxconfig()
  199. if not WxDependency.wx_found:
  200. raise DependencyException('Wx-config not found.')
  201. self.is_found = False
  202. p = subprocess.Popen([self.wxc, '--version'],
  203. stdout=subprocess.PIPE,
  204. stderr=subprocess.PIPE)
  205. out = p.communicate()[0]
  206. if p.returncode != 0:
  207. mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
  208. self.cargs = []
  209. self.libs = []
  210. else:
  211. self.modversion = out.decode().strip()
  212. version_req = kwargs.get('version', None)
  213. if version_req is not None:
  214. if not mesonlib.version_compare(self.modversion, version_req):
  215. mlog.log('Wxwidgets version %s does not fullfill requirement %s' %\
  216. (self.modversion, version_req))
  217. return
  218. mlog.log('Dependency wxwidgets found:', mlog.green('YES'))
  219. self.is_found = True
  220. self.requested_modules = self.get_requested(kwargs)
  221. # wx-config seems to have a cflags as well but since it requires C++,
  222. # this should be good, at least for now.
  223. p = subprocess.Popen([self.wxc, '--cxxflags'],
  224. stdout=subprocess.PIPE,
  225. stderr=subprocess.PIPE)
  226. out = p.communicate()[0]
  227. if p.returncode != 0:
  228. raise RuntimeError('Could not generate cargs for wxwidgets.')
  229. self.cargs = out.decode().split()
  230. p = subprocess.Popen([self.wxc, '--libs'] + self.requested_modules,
  231. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  232. out = p.communicate()[0]
  233. if p.returncode != 0:
  234. raise RuntimeError('Could not generate libs for wxwidgets.')
  235. self.libs = out.decode().split()
  236. def get_requested(self, kwargs):
  237. modules = 'modules'
  238. if not modules in kwargs:
  239. return []
  240. candidates = kwargs[modules]
  241. if isinstance(candidates, str):
  242. return [candidates]
  243. for c in candidates:
  244. if not isinstance(c, str):
  245. raise DependencyException('wxwidgets module argument is not a string.')
  246. return candidates
  247. def get_modversion(self):
  248. return self.modversion
  249. def get_compile_args(self):
  250. return self.cargs
  251. def get_link_args(self):
  252. return self.libs
  253. def check_wxconfig(self):
  254. for wxc in ['wx-config-3.0', 'wx-config']:
  255. try:
  256. p = subprocess.Popen([wxc, '--version'], stdout=subprocess.PIPE,
  257. stderr=subprocess.PIPE)
  258. out = p.communicate()[0]
  259. if p.returncode == 0:
  260. mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
  261. '(%s)' % out.decode().strip())
  262. self.wxc = wxc
  263. WxDependency.wx_found = True
  264. return
  265. except Exception:
  266. pass
  267. WxDependency.wxconfig_found = False
  268. mlog.log('Found wx-config:', mlog.red('NO'))
  269. def found(self):
  270. return self.is_found
  271. class ExternalProgram():
  272. def __init__(self, name, fullpath=None, silent=False, search_dir=None):
  273. self.name = name
  274. self.fullpath = None
  275. if fullpath is not None:
  276. if not isinstance(fullpath, list):
  277. self.fullpath = [fullpath]
  278. else:
  279. self.fullpath = fullpath
  280. else:
  281. self.fullpath = [shutil.which(name)]
  282. if self.fullpath[0] is None and search_dir is not None:
  283. trial = os.path.join(search_dir, name)
  284. suffix = os.path.splitext(trial)[-1].lower()[1:]
  285. if mesonlib.is_windows() and (suffix == 'exe' or suffix == 'com'\
  286. or suffix == 'bat'):
  287. self.fullpath = [trial]
  288. elif not mesonlib.is_windows() and os.access(trial, os.X_OK):
  289. self.fullpath = [trial]
  290. else:
  291. # Now getting desperate. Maybe it is a script file that is a) not chmodded
  292. # executable or b) we are on windows so they can't be directly executed.
  293. try:
  294. first_line = open(trial).readline().strip()
  295. if first_line.startswith('#!'):
  296. commands = first_line[2:].split('#')[0].strip().split()
  297. if mesonlib.is_windows():
  298. # Windows does not have /usr/bin.
  299. commands[0] = commands[0].split('/')[-1]
  300. if commands[0] == 'env':
  301. commands = commands[1:]
  302. self.fullpath = commands + [trial]
  303. except Exception:
  304. pass
  305. if not silent:
  306. if self.found():
  307. mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
  308. '(%s)' % ' '.join(self.fullpath))
  309. else:
  310. mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
  311. def found(self):
  312. return self.fullpath[0] is not None
  313. def get_command(self):
  314. return self.fullpath
  315. def get_name(self):
  316. return self.name
  317. class ExternalLibrary(Dependency):
  318. def __init__(self, name, fullpath=None, silent=False):
  319. super().__init__()
  320. self.name = name
  321. self.fullpath = fullpath
  322. if not silent:
  323. if self.found():
  324. mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'),
  325. '(%s)' % self.fullpath)
  326. else:
  327. mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
  328. def found(self):
  329. return self.fullpath is not None
  330. def get_link_args(self):
  331. if self.found():
  332. return [self.fullpath]
  333. return []
  334. class BoostDependency(Dependency):
  335. # Some boost libraries have different names for
  336. # their sources and libraries. This dict maps
  337. # between the two.
  338. name2lib = {'test' : 'unit_test_framework'}
  339. def __init__(self, environment, kwargs):
  340. Dependency.__init__(self)
  341. self.name = 'boost'
  342. try:
  343. self.boost_root = os.environ['BOOST_ROOT']
  344. if not os.path.isabs(self.boost_root):
  345. raise DependencyException('BOOST_ROOT must be an absolute path.')
  346. except KeyError:
  347. self.boost_root = None
  348. if self.boost_root is None:
  349. self.incdir = '/usr/include/boost'
  350. else:
  351. self.incdir = os.path.join(self.boost_root, 'include/boost')
  352. self.src_modules = {}
  353. self.lib_modules = {}
  354. self.lib_modules_mt = {}
  355. self.detect_version()
  356. self.requested_modules = self.get_requested(kwargs)
  357. module_str = ', '.join(self.requested_modules)
  358. if self.version is not None:
  359. self.detect_src_modules()
  360. self.detect_lib_modules()
  361. self.validate_requested()
  362. if self.boost_root is not None:
  363. info = self.version + ', ' + self.boost_root
  364. else:
  365. info = self.version
  366. mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'),
  367. '(' + info + ')')
  368. else:
  369. mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO'))
  370. def get_compile_args(self):
  371. args = []
  372. if self.boost_root is not None:
  373. args.append('-I' + os.path.join(self.boost_root, 'include'))
  374. return args
  375. def get_requested(self, kwargs):
  376. modules = 'modules'
  377. if not modules in kwargs:
  378. raise DependencyException('Boost dependency must specify "%s" keyword.' % modules)
  379. candidates = kwargs[modules]
  380. if isinstance(candidates, str):
  381. return [candidates]
  382. for c in candidates:
  383. if not isinstance(c, str):
  384. raise DependencyException('Boost module argument is not a string.')
  385. return candidates
  386. def validate_requested(self):
  387. for m in self.requested_modules:
  388. if m not in self.src_modules:
  389. raise DependencyException('Requested Boost module "%s" not found.' % m)
  390. def found(self):
  391. return self.version is not None
  392. def get_version(self):
  393. return self.version
  394. def detect_version(self):
  395. try:
  396. ifile = open(os.path.join(self.incdir, 'version.hpp'))
  397. except FileNotFoundError:
  398. self.version = None
  399. return
  400. for line in ifile:
  401. if line.startswith("#define") and 'BOOST_LIB_VERSION' in line:
  402. ver = line.split()[-1]
  403. ver = ver[1:-1]
  404. self.version = ver.replace('_', '.')
  405. return
  406. self.version = None
  407. def detect_src_modules(self):
  408. for entry in os.listdir(self.incdir):
  409. entry = os.path.join(self.incdir, entry)
  410. if stat.S_ISDIR(os.stat(entry).st_mode):
  411. self.src_modules[os.path.split(entry)[-1]] = True
  412. def detect_lib_modules(self):
  413. globber = 'libboost_*.so' # FIXME, make platform independent.
  414. if self.boost_root is None:
  415. libdirs = mesonlib.get_library_dirs()
  416. else:
  417. libdirs = [os.path.join(self.boost_root, 'lib')]
  418. for libdir in libdirs:
  419. for entry in glob.glob(os.path.join(libdir, globber)):
  420. lib = os.path.basename(entry)
  421. name = lib.split('.')[0].split('_', 1)[-1]
  422. # I'm not 100% sure what to do here. Some distros
  423. # have modules such as thread only as -mt versions.
  424. if entry.endswith('-mt.so'):
  425. self.lib_modules_mt[name] = True
  426. else:
  427. self.lib_modules[name] = True
  428. def get_link_args(self):
  429. args = []
  430. if self.boost_root:
  431. # FIXME, these are in gcc format, not msvc.
  432. # On the other hand, so are the args that
  433. # pkg-config returns.
  434. args.append('-L' + os.path.join(self.boost_root, 'lib'))
  435. for module in self.requested_modules:
  436. module = BoostDependency.name2lib.get(module, module)
  437. if module in self.lib_modules or module in self.lib_modules_mt:
  438. linkcmd = '-lboost_' + module
  439. args.append(linkcmd)
  440. # FIXME a hack, but Boost's testing framework has a lot of
  441. # different options and it's hard to determine what to do
  442. # without feedback from actual users. Update this
  443. # as we get more bug reports.
  444. if module == 'unit_testing_framework':
  445. args.append('-lboost_test_exec_monitor')
  446. elif module + '-mt' in self.lib_modules_mt:
  447. linkcmd = '-lboost_' + module + '-mt'
  448. args.append(linkcmd)
  449. if module == 'unit_testing_framework':
  450. args.append('-lboost_test_exec_monitor-mt')
  451. return args
  452. def get_sources(self):
  453. return []
  454. def need_threads(self):
  455. return 'thread' in self.requested_modules
  456. class GTestDependency(Dependency):
  457. def __init__(self, environment, kwargs):
  458. Dependency.__init__(self)
  459. self.main = kwargs.get('main', False)
  460. self.name = 'gtest'
  461. self.libname = 'libgtest.so'
  462. self.libmain_name = 'libgtest_main.so'
  463. self.include_dir = '/usr/include'
  464. self.src_include_dir = '/usr/src/gtest'
  465. self.src_dir = '/usr/src/gtest/src'
  466. self.all_src = mesonlib.File.from_absolute_file(
  467. os.path.join(self.src_dir, 'gtest-all.cc'))
  468. self.main_src = mesonlib.File.from_absolute_file(
  469. os.path.join(self.src_dir, 'gtest_main.cc'))
  470. self.detect()
  471. def found(self):
  472. return self.is_found
  473. def detect(self):
  474. trial_dirs = mesonlib.get_library_dirs()
  475. glib_found = False
  476. gmain_found = False
  477. for d in trial_dirs:
  478. if os.path.isfile(os.path.join(d, self.libname)):
  479. glib_found = True
  480. if os.path.isfile(os.path.join(d, self.libmain_name)):
  481. gmain_found = True
  482. if glib_found and gmain_found:
  483. self.is_found = True
  484. self.compile_args = []
  485. self.link_args = ['-lgtest']
  486. if self.main:
  487. self.link_args.append('-lgtest_main')
  488. self.sources = []
  489. mlog.log('Dependency GTest found:', mlog.green('YES'), '(prebuilt)')
  490. elif os.path.exists(self.src_dir):
  491. self.is_found = True
  492. self.compile_args = ['-I' + self.src_include_dir]
  493. self.link_args = []
  494. if self.main:
  495. self.sources = [self.all_src, self.main_src]
  496. else:
  497. self.sources = [self.all_src]
  498. mlog.log('Dependency GTest found:', mlog.green('YES'), '(building self)')
  499. else:
  500. mlog.log('Dependency GTest found:', mlog.red('NO'))
  501. self.is_found = False
  502. return self.is_found
  503. def get_compile_args(self):
  504. arr = []
  505. if self.include_dir != '/usr/include':
  506. arr.append('-I' + self.include_dir)
  507. arr.append('-I' + self.src_include_dir)
  508. return arr
  509. def get_link_args(self):
  510. return self.link_args
  511. def get_version(self):
  512. return '1.something_maybe'
  513. def get_sources(self):
  514. return self.sources
  515. def need_threads(self):
  516. return True
  517. class GMockDependency(Dependency):
  518. def __init__(self, environment, kwargs):
  519. Dependency.__init__(self)
  520. # GMock may be a library or just source.
  521. # Work with both.
  522. self.name = 'gmock'
  523. self.libname = 'libgmock.so'
  524. trial_dirs = mesonlib.get_library_dirs()
  525. gmock_found = False
  526. for d in trial_dirs:
  527. if os.path.isfile(os.path.join(d, self.libname)):
  528. gmock_found = True
  529. if gmock_found:
  530. self.is_found = True
  531. self.compile_args = []
  532. self.link_args = ['-lgmock']
  533. self.sources = []
  534. mlog.log('Dependency GMock found:', mlog.green('YES'), '(prebuilt)')
  535. return
  536. for d in ['/usr/src/gmock/src', '/usr/src/gmock']:
  537. if os.path.exists(d):
  538. self.is_found = True
  539. # Yes, we need both because there are multiple
  540. # versions of gmock that do different things.
  541. self.compile_args = ['-I/usr/src/gmock', '-I/usr/src/gmock/src']
  542. self.link_args = []
  543. all_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock-all.cc'))
  544. main_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock_main.cc'))
  545. if kwargs.get('main', False):
  546. self.sources = [all_src, main_src]
  547. else:
  548. self.sources = [all_src]
  549. mlog.log('Dependency GMock found:', mlog.green('YES'), '(building self)')
  550. return
  551. mlog.log('Dependency GMock found:', mlog.red('NO'))
  552. self.is_found = False
  553. def get_version(self):
  554. return '1.something_maybe'
  555. def get_compile_args(self):
  556. return self.compile_args
  557. def get_sources(self):
  558. return self.sources
  559. def get_link_args(self):
  560. return self.link_args
  561. def found(self):
  562. return self.is_found
  563. class Qt5Dependency(Dependency):
  564. def __init__(self, environment, kwargs):
  565. Dependency.__init__(self)
  566. self.name = 'qt5'
  567. self.root = '/usr'
  568. mods = kwargs.get('modules', [])
  569. self.cargs = []
  570. self.largs = []
  571. self.is_found = False
  572. if isinstance(mods, str):
  573. mods = [mods]
  574. if len(mods) == 0:
  575. raise DependencyException('No Qt5 modules specified.')
  576. type_text = 'native'
  577. if environment.is_cross_build() and kwargs.get('native', False):
  578. type_text = 'cross'
  579. self.pkgconfig_detect(mods, environment, kwargs)
  580. elif not environment.is_cross_build() and shutil.which('pkg-config') is not None:
  581. self.pkgconfig_detect(mods, environment, kwargs)
  582. elif shutil.which('qmake') is not None:
  583. self.qmake_detect(mods, kwargs)
  584. else:
  585. self.version = 'none'
  586. if not self.is_found:
  587. mlog.log('Qt5 %s dependency found: ' % type_text, mlog.red('NO'))
  588. else:
  589. mlog.log('Qt5 %s dependency found: ' % type_text, mlog.green('YES'))
  590. def pkgconfig_detect(self, mods, environment, kwargs):
  591. modules = []
  592. for module in mods:
  593. modules.append(PkgConfigDependency('Qt5' + module, environment, kwargs))
  594. for m in modules:
  595. self.cargs += m.get_compile_args()
  596. self.largs += m.get_link_args()
  597. self.is_found = True
  598. self.version = modules[0].modversion
  599. def qmake_detect(self, mods, kwargs):
  600. pc = subprocess.Popen(['qmake', '-v'], stdout=subprocess.PIPE,
  601. stderr=subprocess.PIPE)
  602. (stdo, _) = pc.communicate()
  603. if pc.returncode != 0:
  604. return
  605. stdo = stdo.decode()
  606. if not 'version 5' in stdo:
  607. mlog.log('QMake is not for Qt5.')
  608. return
  609. self.version = re.search('5(\.\d+)+', stdo).group(0)
  610. (stdo, _) = subprocess.Popen(['qmake', '-query'], stdout=subprocess.PIPE).communicate()
  611. qvars = {}
  612. for line in stdo.decode().split('\n'):
  613. line = line.strip()
  614. if line == '':
  615. continue
  616. (k, v) = tuple(line.split(':', 1))
  617. qvars[k] = v
  618. if mesonlib.is_osx():
  619. return self.framework_detect(qvars, mods, kwargs)
  620. incdir = qvars['QT_INSTALL_HEADERS']
  621. self.cargs.append('-I' + incdir)
  622. libdir = qvars['QT_INSTALL_LIBS']
  623. bindir = qvars['QT_INSTALL_BINS']
  624. #self.largs.append('-L' + libdir)
  625. for module in mods:
  626. mincdir = os.path.join(incdir, 'Qt' + module)
  627. self.cargs.append('-I' + mincdir)
  628. libfile = os.path.join(libdir, 'Qt5' + module + '.lib')
  629. if not os.path.isfile(libfile):
  630. # MinGW links directly to .dll, not to .lib.
  631. libfile = os.path.join(bindir, 'Qt5' + module + '.dll')
  632. self.largs.append(libfile)
  633. self.is_found = True
  634. def framework_detect(self, qvars, modules, kwargs):
  635. libdir = qvars['QT_INSTALL_LIBS']
  636. for m in modules:
  637. fname = 'Qt' + m
  638. fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir)
  639. self.cargs.append('-F' + libdir)
  640. if fwdep.found():
  641. self.is_found = True
  642. self.cargs += fwdep.get_compile_args()
  643. self.largs += fwdep.get_link_args()
  644. def get_version(self):
  645. return self.version
  646. def get_compile_args(self):
  647. return self.cargs
  648. def get_sources(self):
  649. return []
  650. def get_link_args(self):
  651. return self.largs
  652. def found(self):
  653. return self.is_found
  654. def get_exe_args(self):
  655. # Originally this was -fPIE but nowadays the default
  656. # for upstream and distros seems to be -reduce-relocations
  657. # which requires -fPIC. This may cause a performance
  658. # penalty when using self-built Qt or on platforms
  659. # where -fPIC is not required. If this is an issue
  660. # for you, patches are welcome.
  661. # Fix this to be more portable, especially to MSVC.
  662. return ['-fPIC']
  663. class Qt4Dependency(Dependency):
  664. def __init__(self, environment, kwargs):
  665. Dependency.__init__(self)
  666. self.name = 'qt4'
  667. self.root = '/usr'
  668. self.modules = []
  669. mods = kwargs.get('modules', [])
  670. if isinstance(mods, str):
  671. mods = [mods]
  672. for module in mods:
  673. self.modules.append(PkgConfigDependency('Qt' + module, environment, kwargs))
  674. if len(self.modules) == 0:
  675. raise DependencyException('No Qt4 modules specified.')
  676. def get_version(self):
  677. return self.modules[0].get_version()
  678. def get_compile_args(self):
  679. args = []
  680. for m in self.modules:
  681. args += m.get_compile_args()
  682. return args
  683. def get_sources(self):
  684. return []
  685. def get_link_args(self):
  686. args = []
  687. for module in self.modules:
  688. args += module.get_link_args()
  689. return args
  690. def found(self):
  691. for i in self.modules:
  692. if not i.found():
  693. return False
  694. return True
  695. class GnuStepDependency(Dependency):
  696. def __init__(self, environment, kwargs):
  697. Dependency.__init__(self)
  698. self.modules = kwargs.get('modules', [])
  699. self.detect()
  700. def detect(self):
  701. confprog = 'gnustep-config'
  702. try:
  703. gp = subprocess.Popen([confprog, '--help'],
  704. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  705. gp.communicate()
  706. except FileNotFoundError:
  707. self.args = None
  708. mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)')
  709. return
  710. if gp.returncode != 0:
  711. self.args = None
  712. mlog.log('Dependency GnuStep found:', mlog.red('NO'))
  713. return
  714. if 'gui' in self.modules:
  715. arg = '--gui-libs'
  716. else:
  717. arg = '--base-libs'
  718. fp = subprocess.Popen([confprog, '--objc-flags'],
  719. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  720. (flagtxt, flagerr) = fp.communicate()
  721. flagtxt = flagtxt.decode()
  722. flagerr = flagerr.decode()
  723. if fp.returncode != 0:
  724. raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr))
  725. args = flagtxt.split()
  726. self.args = self.filter_arsg(args)
  727. fp = subprocess.Popen([confprog, arg],
  728. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  729. (libtxt, liberr) = fp.communicate()
  730. libtxt = libtxt.decode()
  731. liberr = liberr.decode()
  732. if fp.returncode != 0:
  733. raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr))
  734. self.libs = self.weird_filter(libtxt.split())
  735. mlog.log('Dependency GnuStep found:', mlog.green('YES'))
  736. def weird_filter(self, elems):
  737. """When building packages, the output of the enclosing Make
  738. is sometimes mixed among the subprocess output. I have no idea
  739. why. As a hack filter out everything that is not a flag."""
  740. return [e for e in elems if e.startswith('-')]
  741. def filter_arsg(self, args):
  742. """gnustep-config returns a bunch of garbage args such
  743. as -O2 and so on. Drop everything that is not needed."""
  744. result = []
  745. for f in args:
  746. if f.startswith('-D') or f.startswith('-f') or \
  747. f.startswith('-I') or f == '-pthread' or\
  748. (f.startswith('-W') and not f == '-Wall'):
  749. result.append(f)
  750. return result
  751. def found(self):
  752. return self.args is not None
  753. def get_compile_args(self):
  754. if self.args is None:
  755. return []
  756. return self.args
  757. def get_link_args(self):
  758. return self.libs
  759. class AppleFrameworks(Dependency):
  760. def __init__(self, environment, kwargs):
  761. Dependency.__init__(self)
  762. modules = kwargs.get('modules', [])
  763. if isinstance(modules, str):
  764. modules = [modules]
  765. if len(modules) == 0:
  766. raise DependencyException("AppleFrameworks dependency requires at least one module.")
  767. self.frameworks = modules
  768. def get_link_args(self):
  769. args = []
  770. for f in self.frameworks:
  771. args.append('-framework')
  772. args.append(f)
  773. return args
  774. def found(self):
  775. return mesonlib.is_osx()
  776. class GLDependency(Dependency):
  777. def __init__(self, environment, kwargs):
  778. Dependency.__init__(self)
  779. self.is_found = False
  780. self.cargs = []
  781. self.linkargs = []
  782. try:
  783. pcdep = PkgConfigDependency('gl', environment, kwargs)
  784. if pcdep.found():
  785. self.is_found = True
  786. self.cargs = pcdep.get_compile_args()
  787. self.linkargs = pcdep.get_link_args()
  788. return
  789. except Exception:
  790. pass
  791. if mesonlib.is_osx():
  792. self.is_found = True
  793. self.linkargs = ['-framework', 'OpenGL']
  794. return
  795. if mesonlib.is_windows():
  796. self.is_found = True
  797. return
  798. def get_link_args(self):
  799. return self.linkargs
  800. # There are three different ways of depending on SDL2:
  801. # sdl2-config, pkg-config and OSX framework
  802. class SDL2Dependency(Dependency):
  803. def __init__(self, environment, kwargs):
  804. Dependency.__init__(self)
  805. self.is_found = False
  806. self.cargs = []
  807. self.linkargs = []
  808. sdlconf = shutil.which('sdl2-config')
  809. if sdlconf:
  810. pc = subprocess.Popen(['sdl2-config', '--cflags'],
  811. stdout=subprocess.PIPE,
  812. stderr=subprocess.DEVNULL)
  813. (stdo, _) = pc.communicate()
  814. self.cargs = stdo.decode().strip().split()
  815. pc = subprocess.Popen(['sdl2-config', '--libs'],
  816. stdout=subprocess.PIPE,
  817. stderr=subprocess.DEVNULL)
  818. (stdo, _) = pc.communicate()
  819. self.linkargs = stdo.decode().strip().split()
  820. self.is_found = True
  821. mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'), '(%s)' % sdlconf)
  822. return
  823. try:
  824. pcdep = PkgConfigDependency('sdl2', kwargs)
  825. if pcdep.found():
  826. self.is_found = True
  827. self.cargs = pcdep.get_compile_args()
  828. self.linkargs = pcdep.get_link_args()
  829. return
  830. except Exception:
  831. pass
  832. if mesonlib.is_osx():
  833. fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True))
  834. if fwdep.found():
  835. self.is_found = True
  836. self.cargs = fwdep.get_compile_args()
  837. self.linkargs = fwdep.get_link_args()
  838. return
  839. mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO'))
  840. def get_compile_args(self):
  841. return self.cargs
  842. def get_link_args(self):
  843. return self.linkargs
  844. def found(self):
  845. return self.is_found
  846. class ExtraFrameworkDependency(Dependency):
  847. def __init__(self, name, required, path=None):
  848. Dependency.__init__(self)
  849. self.name = None
  850. self.detect(name, path)
  851. if self.found():
  852. mlog.log('Dependency', mlog.bold(name), 'found:', mlog.green('YES'),
  853. os.path.join(self.path, self.name))
  854. else:
  855. mlog.log('Dependency', name, 'found:', mlog.red('NO'))
  856. def detect(self, name, path):
  857. lname = name.lower()
  858. if path is None:
  859. paths = ['/Library/Frameworks']
  860. else:
  861. paths = [path]
  862. for p in paths:
  863. for d in os.listdir(p):
  864. fullpath = os.path.join(p, d)
  865. if lname != d.split('.')[0].lower():
  866. continue
  867. if not stat.S_ISDIR(os.stat(fullpath).st_mode):
  868. continue
  869. self.path = p
  870. self.name = d
  871. return
  872. def get_compile_args(self):
  873. if self.found():
  874. return ['-I' + os.path.join(self.path, self.name, 'Headers')]
  875. return []
  876. def get_link_args(self):
  877. if self.found():
  878. return ['-F' + self.path, '-framework', self.name.split('.')[0]]
  879. return []
  880. def found(self):
  881. return self.name is not None
  882. def get_dep_identifier(name, kwargs):
  883. elements = [name]
  884. modlist = kwargs.get('modules', [])
  885. if isinstance(modlist, str):
  886. modlist = [modlist]
  887. for module in modlist:
  888. elements.append(module)
  889. return '/'.join(elements) + '/main' + str(kwargs.get('main', False))
  890. def find_external_dependency(name, environment, kwargs):
  891. required = kwargs.get('required', True)
  892. if not isinstance(required, bool):
  893. raise DependencyException('Keyword "required" must be a boolean.')
  894. lname = name.lower()
  895. if lname in packages:
  896. dep = packages[lname](environment, kwargs)
  897. if required and not dep.found():
  898. raise DependencyException('Dependency "%s" not found' % name)
  899. return dep
  900. pkg_exc = None
  901. pkgdep = None
  902. try:
  903. pkgdep = PkgConfigDependency(name, environment, kwargs)
  904. if pkgdep.found():
  905. return pkgdep
  906. except Exception as e:
  907. pkg_exc = e
  908. if mesonlib.is_osx():
  909. fwdep = ExtraFrameworkDependency(name, required)
  910. if required and not fwdep.found():
  911. raise DependencyException('Dependency "%s" not found' % name)
  912. return fwdep
  913. if pkg_exc is not None:
  914. raise pkg_exc
  915. mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO'))
  916. return pkgdep
  917. # This has to be at the end so the classes it references
  918. # are defined.
  919. packages = {'boost': BoostDependency,
  920. 'gtest': GTestDependency,
  921. 'gmock': GMockDependency,
  922. 'qt5': Qt5Dependency,
  923. 'qt4': Qt4Dependency,
  924. 'gnustep': GnuStepDependency,
  925. 'appleframeworks': AppleFrameworks,
  926. 'wxwidgets' : WxDependency,
  927. 'sdl2' : SDL2Dependency,
  928. 'gl' : GLDependency,
  929. }