qt5.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2006-2018 (ita)
  4. """
  5. This tool helps with finding Qt5 tools and libraries,
  6. and also provides syntactic sugar for using Qt5 tools.
  7. The following snippet illustrates the tool usage::
  8. def options(opt):
  9. opt.load('compiler_cxx qt5')
  10. def configure(conf):
  11. conf.load('compiler_cxx qt5')
  12. def build(bld):
  13. bld(
  14. features = 'qt5 cxx cxxprogram',
  15. uselib = 'QT5CORE QT5GUI QT5OPENGL QT5SVG',
  16. source = 'main.cpp textures.qrc aboutDialog.ui',
  17. target = 'window',
  18. )
  19. Here, the UI description and resource files will be processed
  20. to generate code.
  21. Usage
  22. =====
  23. Load the "qt5" tool.
  24. You also need to edit your sources accordingly:
  25. - the normal way of doing things is to have your C++ files
  26. include the .moc file.
  27. This is regarded as the best practice (and provides much faster
  28. compilations).
  29. It also implies that the include paths have beenset properly.
  30. - to have the include paths added automatically, use the following::
  31. from waflib.TaskGen import feature, before_method, after_method
  32. @feature('cxx')
  33. @after_method('process_source')
  34. @before_method('apply_incpaths')
  35. def add_includes_paths(self):
  36. incs = set(self.to_list(getattr(self, 'includes', '')))
  37. for x in self.compiled_tasks:
  38. incs.add(x.inputs[0].parent.path_from(self.path))
  39. self.includes = sorted(incs)
  40. Note: another tool provides Qt processing that does not require
  41. .moc includes, see 'playground/slow_qt/'.
  42. A few options (--qt{dir,bin,...}) and environment variables
  43. (QT5_{ROOT,DIR,MOC,UIC,XCOMPILE}) allow finer tuning of the tool,
  44. tool path selection, etc; please read the source for more info.
  45. The detection uses pkg-config on Linux by default. To force static library detection use:
  46. QT5_XCOMPILE=1 QT5_FORCE_STATIC=1 waf configure
  47. """
  48. from __future__ import with_statement
  49. try:
  50. from xml.sax import make_parser
  51. from xml.sax.handler import ContentHandler
  52. except ImportError:
  53. has_xml = False
  54. ContentHandler = object
  55. else:
  56. has_xml = True
  57. import os, sys, re
  58. from waflib.Tools import cxx
  59. from waflib import Task, Utils, Options, Errors, Context
  60. from waflib.TaskGen import feature, after_method, extension, before_method
  61. from waflib.Configure import conf
  62. from waflib import Logs
  63. MOC_H = ['.h', '.hpp', '.hxx', '.hh']
  64. """
  65. File extensions associated to .moc files
  66. """
  67. EXT_RCC = ['.qrc']
  68. """
  69. File extension for the resource (.qrc) files
  70. """
  71. EXT_UI = ['.ui']
  72. """
  73. File extension for the user interface (.ui) files
  74. """
  75. EXT_QT5 = ['.cpp', '.cc', '.cxx', '.C']
  76. """
  77. File extensions of C++ files that may require a .moc processing
  78. """
  79. class qxx(Task.classes['cxx']):
  80. """
  81. Each C++ file can have zero or several .moc files to create.
  82. They are known only when the files are scanned (preprocessor)
  83. To avoid scanning the c++ files each time (parsing C/C++), the results
  84. are retrieved from the task cache (bld.node_deps/bld.raw_deps).
  85. The moc tasks are also created *dynamically* during the build.
  86. """
  87. def __init__(self, *k, **kw):
  88. Task.Task.__init__(self, *k, **kw)
  89. self.moc_done = 0
  90. def runnable_status(self):
  91. """
  92. Compute the task signature to make sure the scanner was executed. Create the
  93. moc tasks by using :py:meth:`waflib.Tools.qt5.qxx.add_moc_tasks` (if necessary),
  94. then postpone the task execution (there is no need to recompute the task signature).
  95. """
  96. if self.moc_done:
  97. return Task.Task.runnable_status(self)
  98. else:
  99. for t in self.run_after:
  100. if not t.hasrun:
  101. return Task.ASK_LATER
  102. self.add_moc_tasks()
  103. return Task.Task.runnable_status(self)
  104. def create_moc_task(self, h_node, m_node):
  105. """
  106. If several libraries use the same classes, it is possible that moc will run several times (Issue 1318)
  107. It is not possible to change the file names, but we can assume that the moc transformation will be identical,
  108. and the moc tasks can be shared in a global cache.
  109. """
  110. try:
  111. moc_cache = self.generator.bld.moc_cache
  112. except AttributeError:
  113. moc_cache = self.generator.bld.moc_cache = {}
  114. try:
  115. return moc_cache[h_node]
  116. except KeyError:
  117. tsk = moc_cache[h_node] = Task.classes['moc'](env=self.env, generator=self.generator)
  118. tsk.set_inputs(h_node)
  119. tsk.set_outputs(m_node)
  120. tsk.env.append_unique('MOC_FLAGS', '-i')
  121. if self.generator:
  122. self.generator.tasks.append(tsk)
  123. # direct injection in the build phase (safe because called from the main thread)
  124. gen = self.generator.bld.producer
  125. gen.outstanding.append(tsk)
  126. gen.total += 1
  127. return tsk
  128. else:
  129. # remove the signature, it must be recomputed with the moc task
  130. delattr(self, 'cache_sig')
  131. def add_moc_tasks(self):
  132. """
  133. Creates moc tasks by looking in the list of file dependencies ``bld.raw_deps[self.uid()]``
  134. """
  135. node = self.inputs[0]
  136. bld = self.generator.bld
  137. try:
  138. # compute the signature once to know if there is a moc file to create
  139. self.signature()
  140. except KeyError:
  141. # the moc file may be referenced somewhere else
  142. pass
  143. else:
  144. # remove the signature, it must be recomputed with the moc task
  145. delattr(self, 'cache_sig')
  146. include_nodes = [node.parent] + self.generator.includes_nodes
  147. moctasks = []
  148. mocfiles = set()
  149. for d in bld.raw_deps.get(self.uid(), []):
  150. if not d.endswith('.moc'):
  151. continue
  152. # process that base.moc only once
  153. if d in mocfiles:
  154. continue
  155. mocfiles.add(d)
  156. # find the source associated with the moc file
  157. h_node = None
  158. base2 = d[:-4]
  159. # foo.moc from foo.cpp
  160. prefix = node.name[:node.name.rfind('.')]
  161. if base2 == prefix:
  162. h_node = node
  163. else:
  164. # this deviates from the standard
  165. # if bar.cpp includes foo.moc, then assume it is from foo.h
  166. for x in include_nodes:
  167. for e in MOC_H:
  168. h_node = x.find_node(base2 + e)
  169. if h_node:
  170. break
  171. else:
  172. continue
  173. break
  174. if h_node:
  175. m_node = h_node.change_ext('.moc')
  176. else:
  177. raise Errors.WafError('No source found for %r which is a moc file' % d)
  178. # create the moc task
  179. task = self.create_moc_task(h_node, m_node)
  180. moctasks.append(task)
  181. # simple scheduler dependency: run the moc task before others
  182. self.run_after.update(set(moctasks))
  183. self.moc_done = 1
  184. class trans_update(Task.Task):
  185. """Updates a .ts files from a list of C++ files"""
  186. run_str = '${QT_LUPDATE} ${SRC} -ts ${TGT}'
  187. color = 'BLUE'
  188. class XMLHandler(ContentHandler):
  189. """
  190. Parses ``.qrc`` files
  191. """
  192. def __init__(self):
  193. ContentHandler.__init__(self)
  194. self.buf = []
  195. self.files = []
  196. def startElement(self, name, attrs):
  197. if name == 'file':
  198. self.buf = []
  199. def endElement(self, name):
  200. if name == 'file':
  201. self.files.append(str(''.join(self.buf)))
  202. def characters(self, cars):
  203. self.buf.append(cars)
  204. @extension(*EXT_RCC)
  205. def create_rcc_task(self, node):
  206. "Creates rcc and cxx tasks for ``.qrc`` files"
  207. rcnode = node.change_ext('_rc.%d.cpp' % self.idx)
  208. self.create_task('rcc', node, rcnode)
  209. cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o'))
  210. try:
  211. self.compiled_tasks.append(cpptask)
  212. except AttributeError:
  213. self.compiled_tasks = [cpptask]
  214. return cpptask
  215. @extension(*EXT_UI)
  216. def create_uic_task(self, node):
  217. "Create uic tasks for user interface ``.ui`` definition files"
  218. """
  219. If UIC file is used in more than one bld, we would have a conflict in parallel execution
  220. It is not possible to change the file names (like .self.idx. as for objects) as they have
  221. to be referenced by the source file, but we can assume that the transformation will be identical
  222. and the tasks can be shared in a global cache.
  223. """
  224. try:
  225. uic_cache = self.bld.uic_cache
  226. except AttributeError:
  227. uic_cache = self.bld.uic_cache = {}
  228. if node not in uic_cache:
  229. uictask = uic_cache[node] = self.create_task('ui5', node)
  230. uictask.outputs = [node.parent.find_or_declare(self.env.ui_PATTERN % node.name[:-3])]
  231. @extension('.ts')
  232. def add_lang(self, node):
  233. """Adds all the .ts file into ``self.lang``"""
  234. self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
  235. @feature('qt5')
  236. @before_method('process_source')
  237. def process_mocs(self):
  238. """
  239. Processes MOC files included in headers::
  240. def build(bld):
  241. bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE', moc='foo.h')
  242. The build will run moc on foo.h to create moc_foo.n.cpp. The number in the file name
  243. is provided to avoid name clashes when the same headers are used by several targets.
  244. """
  245. lst = self.to_nodes(getattr(self, 'moc', []))
  246. self.source = self.to_list(getattr(self, 'source', []))
  247. for x in lst:
  248. prefix = x.name[:x.name.rfind('.')] # foo.h -> foo
  249. moc_target = 'moc_%s.%d.cpp' % (prefix, self.idx)
  250. moc_node = x.parent.find_or_declare(moc_target)
  251. self.source.append(moc_node)
  252. self.create_task('moc', x, moc_node)
  253. @feature('qt5')
  254. @after_method('apply_link')
  255. def apply_qt5(self):
  256. """
  257. Adds MOC_FLAGS which may be necessary for moc::
  258. def build(bld):
  259. bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE')
  260. The additional parameters are:
  261. :param lang: list of translation files (\*.ts) to process
  262. :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
  263. :param update: whether to process the C++ files to update the \*.ts files (use **waf --translate**)
  264. :type update: bool
  265. :param langname: if given, transform the \*.ts files into a .qrc files to include in the binary file
  266. :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
  267. """
  268. if getattr(self, 'lang', None):
  269. qmtasks = []
  270. for x in self.to_list(self.lang):
  271. if isinstance(x, str):
  272. x = self.path.find_resource(x + '.ts')
  273. qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.%d.qm' % self.idx)))
  274. if getattr(self, 'update', None) and Options.options.trans_qt5:
  275. cxxnodes = [a.inputs[0] for a in self.compiled_tasks] + [
  276. a.inputs[0] for a in self.tasks if a.inputs and a.inputs[0].name.endswith('.ui')]
  277. for x in qmtasks:
  278. self.create_task('trans_update', cxxnodes, x.inputs)
  279. if getattr(self, 'langname', None):
  280. qmnodes = [x.outputs[0] for x in qmtasks]
  281. rcnode = self.langname
  282. if isinstance(rcnode, str):
  283. rcnode = self.path.find_or_declare(rcnode + ('.%d.qrc' % self.idx))
  284. t = self.create_task('qm2rcc', qmnodes, rcnode)
  285. k = create_rcc_task(self, t.outputs[0])
  286. self.link_task.inputs.append(k.outputs[0])
  287. lst = []
  288. for flag in self.to_list(self.env.CXXFLAGS):
  289. if len(flag) < 2:
  290. continue
  291. f = flag[0:2]
  292. if f in ('-D', '-I', '/D', '/I'):
  293. if (f[0] == '/'):
  294. lst.append('-' + flag[1:])
  295. else:
  296. lst.append(flag)
  297. self.env.append_value('MOC_FLAGS', lst)
  298. @extension(*EXT_QT5)
  299. def cxx_hook(self, node):
  300. """
  301. Re-maps C++ file extensions to the :py:class:`waflib.Tools.qt5.qxx` task.
  302. """
  303. return self.create_compiled_task('qxx', node)
  304. class rcc(Task.Task):
  305. """
  306. Processes ``.qrc`` files
  307. """
  308. color = 'BLUE'
  309. run_str = '${QT_RCC} -name ${tsk.rcname()} ${SRC[0].abspath()} ${RCC_ST} -o ${TGT}'
  310. ext_out = ['.h']
  311. def rcname(self):
  312. return os.path.splitext(self.inputs[0].name)[0]
  313. def scan(self):
  314. """Parse the *.qrc* files"""
  315. if not has_xml:
  316. Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
  317. return ([], [])
  318. parser = make_parser()
  319. curHandler = XMLHandler()
  320. parser.setContentHandler(curHandler)
  321. with open(self.inputs[0].abspath(), 'r') as f:
  322. parser.parse(f)
  323. nodes = []
  324. names = []
  325. root = self.inputs[0].parent
  326. for x in curHandler.files:
  327. nd = root.find_resource(x)
  328. if nd:
  329. nodes.append(nd)
  330. else:
  331. names.append(x)
  332. return (nodes, names)
  333. def quote_flag(self, x):
  334. """
  335. Override Task.quote_flag. QT parses the argument files
  336. differently than cl.exe and link.exe
  337. :param x: flag
  338. :type x: string
  339. :return: quoted flag
  340. :rtype: string
  341. """
  342. return x
  343. class moc(Task.Task):
  344. """
  345. Creates ``.moc`` files
  346. """
  347. color = 'BLUE'
  348. run_str = '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
  349. def quote_flag(self, x):
  350. """
  351. Override Task.quote_flag. QT parses the argument files
  352. differently than cl.exe and link.exe
  353. :param x: flag
  354. :type x: string
  355. :return: quoted flag
  356. :rtype: string
  357. """
  358. return x
  359. class ui5(Task.Task):
  360. """
  361. Processes ``.ui`` files
  362. """
  363. color = 'BLUE'
  364. run_str = '${QT_UIC} ${SRC} -o ${TGT}'
  365. ext_out = ['.h']
  366. class ts2qm(Task.Task):
  367. """
  368. Generates ``.qm`` files from ``.ts`` files
  369. """
  370. color = 'BLUE'
  371. run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
  372. class qm2rcc(Task.Task):
  373. """
  374. Generates ``.qrc`` files from ``.qm`` files
  375. """
  376. color = 'BLUE'
  377. after = 'ts2qm'
  378. def run(self):
  379. """Create a qrc file including the inputs"""
  380. txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
  381. code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
  382. self.outputs[0].write(code)
  383. def configure(self):
  384. """
  385. Besides the configuration options, the environment variable QT5_ROOT may be used
  386. to give the location of the qt5 libraries (absolute path).
  387. The detection uses the program ``pkg-config`` through :py:func:`waflib.Tools.config_c.check_cfg`
  388. """
  389. self.find_qt5_binaries()
  390. self.set_qt5_libs_dir()
  391. self.set_qt5_libs_to_check()
  392. self.set_qt5_defines()
  393. self.find_qt5_libraries()
  394. self.add_qt5_rpath()
  395. self.simplify_qt5_libs()
  396. # warn about this during the configuration too
  397. if not has_xml:
  398. Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
  399. if 'COMPILER_CXX' not in self.env:
  400. self.fatal('No CXX compiler defined: did you forget to configure compiler_cxx first?')
  401. # Qt5 may be compiled with '-reduce-relocations' which requires dependent programs to have -fPIE or -fPIC?
  402. frag = '#include <QApplication>\nint main(int argc, char **argv) {return 0;}\n'
  403. uses = 'QT5CORE QT5WIDGETS QT5GUI'
  404. for flag in [[], '-fPIE', '-fPIC', '-std=c++11' , ['-std=c++11', '-fPIE'], ['-std=c++11', '-fPIC']]:
  405. msg = 'See if Qt files compile '
  406. if flag:
  407. msg += 'with %s' % flag
  408. try:
  409. self.check(features='qt5 cxx', use=uses, uselib_store='qt5', cxxflags=flag, fragment=frag, msg=msg)
  410. except self.errors.ConfigurationError:
  411. pass
  412. else:
  413. break
  414. else:
  415. self.fatal('Could not build a simple Qt application')
  416. # FreeBSD does not add /usr/local/lib and the pkg-config files do not provide it either :-/
  417. if Utils.unversioned_sys_platform() == 'freebsd':
  418. frag = '#include <QApplication>\nint main(int argc, char **argv) { QApplication app(argc, argv); return NULL != (void*) (&app);}\n'
  419. try:
  420. self.check(features='qt5 cxx cxxprogram', use=uses, fragment=frag, msg='Can we link Qt programs on FreeBSD directly?')
  421. except self.errors.ConfigurationError:
  422. self.check(features='qt5 cxx cxxprogram', use=uses, uselib_store='qt5', libpath='/usr/local/lib', fragment=frag, msg='Is /usr/local/lib required?')
  423. @conf
  424. def find_qt5_binaries(self):
  425. """
  426. Detects Qt programs such as qmake, moc, uic, lrelease
  427. """
  428. env = self.env
  429. opt = Options.options
  430. qtdir = getattr(opt, 'qtdir', '')
  431. qtbin = getattr(opt, 'qtbin', '')
  432. paths = []
  433. if qtdir:
  434. qtbin = os.path.join(qtdir, 'bin')
  435. # the qt directory has been given from QT5_ROOT - deduce the qt binary path
  436. if not qtdir:
  437. qtdir = self.environ.get('QT5_ROOT', '')
  438. qtbin = self.environ.get('QT5_BIN') or os.path.join(qtdir, 'bin')
  439. if qtbin:
  440. paths = [qtbin]
  441. # no qtdir, look in the path and in /usr/local/Trolltech
  442. if not qtdir:
  443. paths = self.environ.get('PATH', '').split(os.pathsep)
  444. paths.extend(['/usr/share/qt5/bin', '/usr/local/lib/qt5/bin'])
  445. try:
  446. lst = Utils.listdir('/usr/local/Trolltech/')
  447. except OSError:
  448. pass
  449. else:
  450. if lst:
  451. lst.sort()
  452. lst.reverse()
  453. # keep the highest version
  454. qtdir = '/usr/local/Trolltech/%s/' % lst[0]
  455. qtbin = os.path.join(qtdir, 'bin')
  456. paths.append(qtbin)
  457. # at the end, try to find qmake in the paths given
  458. # keep the one with the highest version
  459. cand = None
  460. prev_ver = ['5', '0', '0']
  461. for qmk in ('qmake-qt5', 'qmake5', 'qmake'):
  462. try:
  463. qmake = self.find_program(qmk, path_list=paths)
  464. except self.errors.ConfigurationError:
  465. pass
  466. else:
  467. try:
  468. version = self.cmd_and_log(qmake + ['-query', 'QT_VERSION']).strip()
  469. except self.errors.WafError:
  470. pass
  471. else:
  472. if version:
  473. new_ver = version.split('.')
  474. if new_ver > prev_ver:
  475. cand = qmake
  476. prev_ver = new_ver
  477. # qmake could not be found easily, rely on qtchooser
  478. if not cand:
  479. try:
  480. self.find_program('qtchooser')
  481. except self.errors.ConfigurationError:
  482. pass
  483. else:
  484. cmd = self.env.QTCHOOSER + ['-qt=5', '-run-tool=qmake']
  485. try:
  486. version = self.cmd_and_log(cmd + ['-query', 'QT_VERSION'])
  487. except self.errors.WafError:
  488. pass
  489. else:
  490. cand = cmd
  491. if cand:
  492. self.env.QMAKE = cand
  493. else:
  494. self.fatal('Could not find qmake for qt5')
  495. self.env.QT_HOST_BINS = qtbin = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_HOST_BINS']).strip()
  496. paths.insert(0, qtbin)
  497. def find_bin(lst, var):
  498. if var in env:
  499. return
  500. for f in lst:
  501. try:
  502. ret = self.find_program(f, path_list=paths)
  503. except self.errors.ConfigurationError:
  504. pass
  505. else:
  506. env[var]=ret
  507. break
  508. find_bin(['uic-qt5', 'uic'], 'QT_UIC')
  509. if not env.QT_UIC:
  510. self.fatal('cannot find the uic compiler for qt5')
  511. self.start_msg('Checking for uic version')
  512. uicver = self.cmd_and_log(env.QT_UIC + ['-version'], output=Context.BOTH)
  513. uicver = ''.join(uicver).strip()
  514. uicver = uicver.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
  515. self.end_msg(uicver)
  516. if uicver.find(' 3.') != -1 or uicver.find(' 4.') != -1:
  517. self.fatal('this uic compiler is for qt3 or qt4, add uic for qt5 to your path')
  518. find_bin(['moc-qt5', 'moc'], 'QT_MOC')
  519. find_bin(['rcc-qt5', 'rcc'], 'QT_RCC')
  520. find_bin(['lrelease-qt5', 'lrelease'], 'QT_LRELEASE')
  521. find_bin(['lupdate-qt5', 'lupdate'], 'QT_LUPDATE')
  522. env.UIC_ST = '%s -o %s'
  523. env.MOC_ST = '-o'
  524. env.ui_PATTERN = 'ui_%s.h'
  525. env.QT_LRELEASE_FLAGS = ['-silent']
  526. env.MOCCPPPATH_ST = '-I%s'
  527. env.MOCDEFINES_ST = '-D%s'
  528. @conf
  529. def set_qt5_libs_dir(self):
  530. env = self.env
  531. qtlibs = getattr(Options.options, 'qtlibs', None) or self.environ.get('QT5_LIBDIR')
  532. if not qtlibs:
  533. try:
  534. qtlibs = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_LIBS']).strip()
  535. except Errors.WafError:
  536. qtdir = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_PREFIX']).strip()
  537. qtlibs = os.path.join(qtdir, 'lib')
  538. self.msg('Found the Qt5 libraries in', qtlibs)
  539. env.QTLIBS = qtlibs
  540. @conf
  541. def find_single_qt5_lib(self, name, uselib, qtlibs, qtincludes, force_static):
  542. env = self.env
  543. if force_static:
  544. exts = ('.a', '.lib')
  545. prefix = 'STLIB'
  546. else:
  547. exts = ('.so', '.lib')
  548. prefix = 'LIB'
  549. def lib_names():
  550. for x in exts:
  551. for k in ('', '5') if Utils.is_win32 else ['']:
  552. for p in ('lib', ''):
  553. yield (p, name, k, x)
  554. for tup in lib_names():
  555. k = ''.join(tup)
  556. path = os.path.join(qtlibs, k)
  557. if os.path.exists(path):
  558. if env.DEST_OS == 'win32':
  559. libval = ''.join(tup[:-1])
  560. else:
  561. libval = name
  562. env.append_unique(prefix + '_' + uselib, libval)
  563. env.append_unique('%sPATH_%s' % (prefix, uselib), qtlibs)
  564. env.append_unique('INCLUDES_' + uselib, qtincludes)
  565. env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, name.replace('Qt5', 'Qt')))
  566. return k
  567. return False
  568. @conf
  569. def find_qt5_libraries(self):
  570. env = self.env
  571. qtincludes = self.environ.get('QT5_INCLUDES') or self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_HEADERS']).strip()
  572. force_static = self.environ.get('QT5_FORCE_STATIC')
  573. try:
  574. if self.environ.get('QT5_XCOMPILE'):
  575. self.fatal('QT5_XCOMPILE Disables pkg-config detection')
  576. self.check_cfg(atleast_pkgconfig_version='0.1')
  577. except self.errors.ConfigurationError:
  578. for i in self.qt5_vars:
  579. uselib = i.upper()
  580. if Utils.unversioned_sys_platform() == 'darwin':
  581. # Since at least qt 4.7.3 each library locates in separate directory
  582. fwk = i.replace('Qt5', 'Qt')
  583. frameworkName = fwk + '.framework'
  584. qtDynamicLib = os.path.join(env.QTLIBS, frameworkName, fwk)
  585. if os.path.exists(qtDynamicLib):
  586. env.append_unique('FRAMEWORK_' + uselib, fwk)
  587. env.append_unique('FRAMEWORKPATH_' + uselib, env.QTLIBS)
  588. self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
  589. else:
  590. self.msg('Checking for %s' % i, False, 'YELLOW')
  591. env.append_unique('INCLUDES_' + uselib, os.path.join(env.QTLIBS, frameworkName, 'Headers'))
  592. else:
  593. ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, force_static)
  594. if not force_static and not ret:
  595. ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, True)
  596. self.msg('Checking for %s' % i, ret, 'GREEN' if ret else 'YELLOW')
  597. else:
  598. path = '%s:%s:%s/pkgconfig:/usr/lib/qt5/lib/pkgconfig:/opt/qt5/lib/pkgconfig:/usr/lib/qt5/lib:/opt/qt5/lib' % (
  599. self.environ.get('PKG_CONFIG_PATH', ''), env.QTLIBS, env.QTLIBS)
  600. for i in self.qt5_vars:
  601. self.check_cfg(package=i, args='--cflags --libs', mandatory=False, force_static=force_static, pkg_config_path=path)
  602. @conf
  603. def simplify_qt5_libs(self):
  604. """
  605. Since library paths make really long command-lines,
  606. and since everything depends on qtcore, remove the qtcore ones from qtgui, etc
  607. """
  608. env = self.env
  609. def process_lib(vars_, coreval):
  610. for d in vars_:
  611. var = d.upper()
  612. if var == 'QTCORE':
  613. continue
  614. value = env['LIBPATH_'+var]
  615. if value:
  616. core = env[coreval]
  617. accu = []
  618. for lib in value:
  619. if lib in core:
  620. continue
  621. accu.append(lib)
  622. env['LIBPATH_'+var] = accu
  623. process_lib(self.qt5_vars, 'LIBPATH_QTCORE')
  624. @conf
  625. def add_qt5_rpath(self):
  626. """
  627. Defines rpath entries for Qt libraries
  628. """
  629. env = self.env
  630. if getattr(Options.options, 'want_rpath', False):
  631. def process_rpath(vars_, coreval):
  632. for d in vars_:
  633. var = d.upper()
  634. value = env['LIBPATH_' + var]
  635. if value:
  636. core = env[coreval]
  637. accu = []
  638. for lib in value:
  639. if var != 'QTCORE':
  640. if lib in core:
  641. continue
  642. accu.append('-Wl,--rpath='+lib)
  643. env['RPATH_' + var] = accu
  644. process_rpath(self.qt5_vars, 'LIBPATH_QTCORE')
  645. @conf
  646. def set_qt5_libs_to_check(self):
  647. self.qt5_vars = Utils.to_list(getattr(self, 'qt5_vars', []))
  648. if not self.qt5_vars:
  649. dirlst = Utils.listdir(self.env.QTLIBS)
  650. pat = self.env.cxxshlib_PATTERN
  651. if Utils.is_win32:
  652. pat = pat.replace('.dll', '.lib')
  653. if self.environ.get('QT5_FORCE_STATIC'):
  654. pat = self.env.cxxstlib_PATTERN
  655. if Utils.unversioned_sys_platform() == 'darwin':
  656. pat = "%s\.framework"
  657. re_qt = re.compile(pat%'Qt5?(?P<name>.*)'+'$')
  658. for x in dirlst:
  659. m = re_qt.match(x)
  660. if m:
  661. self.qt5_vars.append("Qt5%s" % m.group('name'))
  662. if not self.qt5_vars:
  663. self.fatal('cannot find any Qt5 library (%r)' % self.env.QTLIBS)
  664. qtextralibs = getattr(Options.options, 'qtextralibs', None)
  665. if qtextralibs:
  666. self.qt5_vars.extend(qtextralibs.split(','))
  667. @conf
  668. def set_qt5_defines(self):
  669. if sys.platform != 'win32':
  670. return
  671. for x in self.qt5_vars:
  672. y=x.replace('Qt5', 'Qt')[2:].upper()
  673. self.env.append_unique('DEFINES_%s' % x.upper(), 'QT_%s_LIB' % y)
  674. def options(opt):
  675. """
  676. Command-line options
  677. """
  678. opt.add_option('--want-rpath', action='store_true', default=False, dest='want_rpath', help='enable the rpath for qt libraries')
  679. for i in 'qtdir qtbin qtlibs'.split():
  680. opt.add_option('--'+i, type='string', default='', dest=i)
  681. opt.add_option('--translate', action='store_true', help='collect translation strings', dest='trans_qt5', default=False)
  682. opt.add_option('--qtextralibs', type='string', default='', dest='qtextralibs', help='additional qt libraries on the system to add to default ones, comma separated')