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