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