3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
7 # Thomas Nagy, 2006-2010 (ita)
14 This tool helps with finding Qt4 tools and libraries,
15 and also provides syntactic sugar for using Qt4 tools.
17 The following snippet illustrates the tool usage::
20 opt.load('compiler_cxx qt4')
23 conf.load('compiler_cxx qt4')
27 features = 'qt4 cxx cxxprogram',
28 uselib = 'QTCORE QTGUI QTOPENGL QTSVG',
29 source = 'main.cpp textures.qrc aboutDialog.ui',
33 Here, the UI description and resource files will be processed
41 You also need to edit your sources accordingly:
43 - the normal way of doing things is to have your C++ files
44 include the .moc file.
45 This is regarded as the best practice (and provides much faster
47 It also implies that the include paths have beenset properly.
49 - to have the include paths added automatically, use the following::
51 from waflib.TaskGen import feature, before_method, after_method
53 @after_method('process_source')
54 @before_method('apply_incpaths')
55 def add_includes_paths(self):
56 incs = set(self.to_list(getattr(self, 'includes', '')))
57 for x in self.compiled_tasks:
58 incs.add(x.inputs[0].parent.path_from(self.path))
59 self.includes = list(incs)
61 Note: another tool provides Qt processing that does not require
62 .moc includes, see 'playground/slow_qt/'.
64 A few options (--qt{dir,bin,...}) and environment variables
65 (QT4_{ROOT,DIR,MOC,UIC,XCOMPILE}) allow finer tuning of the tool,
66 tool path selection, etc; please read the source for more info.
71 from xml.sax import make_parser
72 from xml.sax.handler import ContentHandler
75 ContentHandler = object
80 from waflib.Tools import cxx
81 from waflib import Task, Utils, Options, Errors, Context
82 from waflib.TaskGen import feature, after_method, extension
83 from waflib.Configure import conf
84 from waflib import Logs
86 MOC_H = ['.h', '.hpp', '.hxx', '.hh']
88 File extensions associated to the .moc files
93 File extension for the resource (.qrc) files
98 File extension for the user interface (.ui) files
101 EXT_QT4 = ['.cpp', '.cc', '.cxx', '.C']
103 File extensions of C++ files that may require a .moc processing
106 QT4_LIBS = "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtXmlPatterns QtWebKit Qt3Support QtHelp QtScript QtDeclarative QtDesigner"
108 class qxx(Task.classes['cxx']):
110 Each C++ file can have zero or several .moc files to create.
111 They are known only when the files are scanned (preprocessor)
112 To avoid scanning the c++ files each time (parsing C/C++), the results
113 are retrieved from the task cache (bld.node_deps/bld.raw_deps).
114 The moc tasks are also created *dynamically* during the build.
117 def __init__(self, *k, **kw):
118 Task.Task.__init__(self, *k, **kw)
121 def runnable_status(self):
123 Compute the task signature to make sure the scanner was executed. Create the
124 moc tasks by using :py:meth:`waflib.Tools.qt4.qxx.add_moc_tasks` (if necessary),
125 then postpone the task execution (there is no need to recompute the task signature).
128 return Task.Task.runnable_status(self)
130 for t in self.run_after:
132 return Task.ASK_LATER
134 return Task.Task.runnable_status(self)
136 def create_moc_task(self, h_node, m_node):
138 If several libraries use the same classes, it is possible that moc will run several times (Issue 1318)
139 It is not possible to change the file names, but we can assume that the moc transformation will be identical,
140 and the moc tasks can be shared in a global cache.
142 The defines passed to moc will then depend on task generator order. If this is not acceptable, then
143 use the tool slow_qt4 instead (and enjoy the slow builds... :-( )
146 moc_cache = self.generator.bld.moc_cache
147 except AttributeError:
148 moc_cache = self.generator.bld.moc_cache = {}
151 return moc_cache[h_node]
153 tsk = moc_cache[h_node] = Task.classes['moc'](env=self.env, generator=self.generator)
154 tsk.set_inputs(h_node)
155 tsk.set_outputs(m_node)
158 self.generator.tasks.append(tsk)
160 # direct injection in the build phase (safe because called from the main thread)
161 gen = self.generator.bld.producer
162 gen.outstanding.insert(0, tsk)
170 ext = Options.options.qt_header_ext.split()
171 except AttributeError:
177 def add_moc_tasks(self):
179 Create the moc tasks by looking in ``bld.raw_deps[self.uid()]``
181 node = self.inputs[0]
182 bld = self.generator.bld
185 # compute the signature once to know if there is a moc file to create
188 # the moc file may be referenced somewhere else
191 # remove the signature, it must be recomputed with the moc task
192 delattr(self, 'cache_sig')
194 include_nodes = [node.parent] + self.generator.includes_nodes
198 for d in bld.raw_deps.get(self.uid(), []):
199 if not d.endswith('.moc'):
202 # process that base.moc only once
207 # find the source associated with the moc file
211 for x in include_nodes:
212 for e in self.moc_h_ext():
213 h_node = x.find_node(base2 + e)
217 m_node = h_node.change_ext('.moc')
220 # foo.cpp -> foo.cpp.moc
222 if base2.endswith(k):
223 for x in include_nodes:
224 h_node = x.find_node(base2)
228 m_node = h_node.change_ext(k + '.moc')
232 raise Errors.WafError('No source found for %r which is a moc file' % d)
234 # create the moc task
235 task = self.create_moc_task(h_node, m_node)
236 moctasks.append(task)
238 # simple scheduler dependency: run the moc task before others
239 self.run_after.update(set(moctasks))
242 class trans_update(Task.Task):
243 """Update a .ts files from a list of C++ files"""
244 run_str = '${QT_LUPDATE} ${SRC} -ts ${TGT}'
246 Task.update_outputs(trans_update)
248 class XMLHandler(ContentHandler):
250 Parser for *.qrc* files
255 def startElement(self, name, attrs):
258 def endElement(self, name):
260 self.files.append(str(''.join(self.buf)))
261 def characters(self, cars):
262 self.buf.append(cars)
265 def create_rcc_task(self, node):
266 "Create rcc and cxx tasks for *.qrc* files"
267 rcnode = node.change_ext('_rc.cpp')
268 self.create_task('rcc', node, rcnode)
269 cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o'))
271 self.compiled_tasks.append(cpptask)
272 except AttributeError:
273 self.compiled_tasks = [cpptask]
277 def create_uic_task(self, node):
279 uictask = self.create_task('ui4', node)
280 uictask.outputs = [self.path.find_or_declare(self.env['ui_PATTERN'] % node.name[:-3])]
283 def add_lang(self, node):
284 """add all the .ts file into self.lang"""
285 self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
288 @after_method('apply_link')
291 Add MOC_FLAGS which may be necessary for moc::
294 bld.program(features='qt4', source='main.cpp', target='app', use='QTCORE')
296 The additional parameters are:
298 :param lang: list of translation files (\*.ts) to process
299 :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
300 :param update: whether to process the C++ files to update the \*.ts files (use **waf --translate**)
302 :param langname: if given, transform the \*.ts files into a .qrc files to include in the binary file
303 :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
305 if getattr(self, 'lang', None):
307 for x in self.to_list(self.lang):
308 if isinstance(x, str):
309 x = self.path.find_resource(x + '.ts')
310 qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.qm')))
312 if getattr(self, 'update', None) and Options.options.trans_qt4:
313 cxxnodes = [a.inputs[0] for a in self.compiled_tasks] + [
314 a.inputs[0] for a in self.tasks if getattr(a, 'inputs', None) and a.inputs[0].name.endswith('.ui')]
316 self.create_task('trans_update', cxxnodes, x.inputs)
318 if getattr(self, 'langname', None):
319 qmnodes = [x.outputs[0] for x in qmtasks]
320 rcnode = self.langname
321 if isinstance(rcnode, str):
322 rcnode = self.path.find_or_declare(rcnode + '.qrc')
323 t = self.create_task('qm2rcc', qmnodes, rcnode)
324 k = create_rcc_task(self, t.outputs[0])
325 self.link_task.inputs.append(k.outputs[0])
328 for flag in self.to_list(self.env['CXXFLAGS']):
329 if len(flag) < 2: continue
331 if f in ('-D', '-I', '/D', '/I'):
333 lst.append('-' + flag[1:])
336 self.env.append_value('MOC_FLAGS', lst)
339 def cxx_hook(self, node):
341 Re-map C++ file extensions to the :py:class:`waflib.Tools.qt4.qxx` task.
343 return self.create_compiled_task('qxx', node)
345 class rcc(Task.Task):
350 run_str = '${QT_RCC} -name ${tsk.rcname()} ${SRC[0].abspath()} ${RCC_ST} -o ${TGT}'
354 return os.path.splitext(self.inputs[0].name)[0]
357 """Parse the *.qrc* files"""
359 Logs.error('no xml support was found, the rcc dependencies will be incomplete!')
362 parser = make_parser()
363 curHandler = XMLHandler()
364 parser.setContentHandler(curHandler)
365 fi = open(self.inputs[0].abspath(), 'r')
373 root = self.inputs[0].parent
374 for x in curHandler.files:
375 nd = root.find_resource(x)
376 if nd: nodes.append(nd)
377 else: names.append(x)
378 return (nodes, names)
380 class moc(Task.Task):
385 run_str = '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
389 return self.outputs[0].path_from(self.generator.bld.launch_node())
391 class ui4(Task.Task):
396 run_str = '${QT_UIC} ${SRC} -o ${TGT}'
399 class ts2qm(Task.Task):
401 Create *.qm* files from *.ts* files
404 run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
406 class qm2rcc(Task.Task):
408 Transform *.qm* files into *.rc* files
414 """Create a qrc file including the inputs"""
415 txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
416 code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
417 self.outputs[0].write(code)
421 Besides the configuration options, the environment variable QT4_ROOT may be used
422 to give the location of the qt4 libraries (absolute path).
424 The detection will use the program *pkg-config* through :py:func:`waflib.Tools.config_c.check_cfg`
426 self.find_qt4_binaries()
427 self.set_qt4_libs_to_check()
428 self.set_qt4_defines()
429 self.find_qt4_libraries()
431 self.simplify_qt4_libs()
434 def find_qt4_binaries(self):
436 opt = Options.options
438 qtdir = getattr(opt, 'qtdir', '')
439 qtbin = getattr(opt, 'qtbin', '')
444 qtbin = os.path.join(qtdir, 'bin')
446 # the qt directory has been given from QT4_ROOT - deduce the qt binary path
448 qtdir = os.environ.get('QT4_ROOT', '')
449 qtbin = os.environ.get('QT4_BIN', None) or os.path.join(qtdir, 'bin')
454 # no qtdir, look in the path and in /usr/local/Trolltech
456 paths = os.environ.get('PATH', '').split(os.pathsep)
457 paths.append('/usr/share/qt4/bin/')
459 lst = Utils.listdir('/usr/local/Trolltech/')
467 # keep the highest version
468 qtdir = '/usr/local/Trolltech/%s/' % lst[0]
469 qtbin = os.path.join(qtdir, 'bin')
472 # at the end, try to find qmake in the paths given
473 # keep the one with the highest version
475 prev_ver = ['4', '0', '0']
476 for qmk in ('qmake-qt4', 'qmake4', 'qmake'):
478 qmake = self.find_program(qmk, path_list=paths)
479 except self.errors.ConfigurationError:
483 version = self.cmd_and_log(qmake + ['-query', 'QT_VERSION']).strip()
484 except self.errors.WafError:
488 new_ver = version.split('.')
489 if new_ver > prev_ver:
493 self.env.QMAKE = cand
495 self.fatal('Could not find qmake for qt4')
497 qtbin = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_BINS']).strip() + os.sep
499 def find_bin(lst, var):
504 ret = self.find_program(f, path_list=paths)
505 except self.errors.ConfigurationError:
511 find_bin(['uic-qt3', 'uic3'], 'QT_UIC3')
512 find_bin(['uic-qt4', 'uic'], 'QT_UIC')
514 self.fatal('cannot find the uic compiler for qt4')
516 self.start_msg('Checking for uic version')
517 uicver = self.cmd_and_log(env.QT_UIC + ["-version"], output=Context.BOTH)
518 uicver = ''.join(uicver).strip()
519 uicver = uicver.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
521 if uicver.find(' 3.') != -1:
522 self.fatal('this uic compiler is for qt3, add uic for qt4 to your path')
524 find_bin(['moc-qt4', 'moc'], 'QT_MOC')
525 find_bin(['rcc-qt4', 'rcc'], 'QT_RCC')
526 find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE')
527 find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE')
529 env['UIC3_ST']= '%s -o %s'
530 env['UIC_ST'] = '%s -o %s'
532 env['ui_PATTERN'] = 'ui_%s.h'
533 env['QT_LRELEASE_FLAGS'] = ['-silent']
534 env.MOCCPPPATH_ST = '-I%s'
535 env.MOCDEFINES_ST = '-D%s'
538 def find_qt4_libraries(self):
539 qtlibs = getattr(Options.options, 'qtlibs', None) or os.environ.get("QT4_LIBDIR", None)
542 qtlibs = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_LIBS']).strip()
543 except Errors.WafError:
544 qtdir = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_PREFIX']).strip() + os.sep
545 qtlibs = os.path.join(qtdir, 'lib')
546 self.msg('Found the Qt4 libraries in', qtlibs)
548 qtincludes = os.environ.get("QT4_INCLUDES", None) or self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_HEADERS']).strip()
550 if not 'PKG_CONFIG_PATH' in os.environ:
551 os.environ['PKG_CONFIG_PATH'] = '%s:%s/pkgconfig:/usr/lib/qt4/lib/pkgconfig:/opt/qt4/lib/pkgconfig:/usr/lib/qt4/lib:/opt/qt4/lib' % (qtlibs, qtlibs)
554 if os.environ.get("QT4_XCOMPILE", None):
555 raise self.errors.ConfigurationError()
556 self.check_cfg(atleast_pkgconfig_version='0.1')
557 except self.errors.ConfigurationError:
558 for i in self.qt4_vars:
560 if Utils.unversioned_sys_platform() == "darwin":
561 # Since at least qt 4.7.3 each library locates in separate directory
562 frameworkName = i + ".framework"
563 qtDynamicLib = os.path.join(qtlibs, frameworkName, i)
564 if os.path.exists(qtDynamicLib):
565 env.append_unique('FRAMEWORK_' + uselib, i)
566 self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
568 self.msg('Checking for %s' % i, False, 'YELLOW')
569 env.append_unique('INCLUDES_' + uselib, os.path.join(qtlibs, frameworkName, 'Headers'))
570 elif env.DEST_OS != "win32":
571 qtDynamicLib = os.path.join(qtlibs, "lib" + i + ".so")
572 qtStaticLib = os.path.join(qtlibs, "lib" + i + ".a")
573 if os.path.exists(qtDynamicLib):
574 env.append_unique('LIB_' + uselib, i)
575 self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
576 elif os.path.exists(qtStaticLib):
577 env.append_unique('LIB_' + uselib, i)
578 self.msg('Checking for %s' % i, qtStaticLib, 'GREEN')
580 self.msg('Checking for %s' % i, False, 'YELLOW')
582 env.append_unique('LIBPATH_' + uselib, qtlibs)
583 env.append_unique('INCLUDES_' + uselib, qtincludes)
584 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
586 # Release library names are like QtCore4
587 for k in ("lib%s.a", "lib%s4.a", "%s.lib", "%s4.lib"):
588 lib = os.path.join(qtlibs, k % i)
589 if os.path.exists(lib):
590 env.append_unique('LIB_' + uselib, i + k[k.find("%s") + 2 : k.find('.')])
591 self.msg('Checking for %s' % i, lib, 'GREEN')
594 self.msg('Checking for %s' % i, False, 'YELLOW')
596 env.append_unique('LIBPATH_' + uselib, qtlibs)
597 env.append_unique('INCLUDES_' + uselib, qtincludes)
598 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
600 # Debug library names are like QtCore4d
601 uselib = i.upper() + "_debug"
602 for k in ("lib%sd.a", "lib%sd4.a", "%sd.lib", "%sd4.lib"):
603 lib = os.path.join(qtlibs, k % i)
604 if os.path.exists(lib):
605 env.append_unique('LIB_' + uselib, i + k[k.find("%s") + 2 : k.find('.')])
606 self.msg('Checking for %s' % i, lib, 'GREEN')
609 self.msg('Checking for %s' % i, False, 'YELLOW')
611 env.append_unique('LIBPATH_' + uselib, qtlibs)
612 env.append_unique('INCLUDES_' + uselib, qtincludes)
613 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
615 for i in self.qt4_vars_debug + self.qt4_vars:
616 self.check_cfg(package=i, args='--cflags --libs', mandatory=False)
619 def simplify_qt4_libs(self):
620 # the libpaths make really long command-lines
621 # remove the qtcore ones from qtgui, etc
623 def process_lib(vars_, coreval):
629 value = env['LIBPATH_'+var]
637 env['LIBPATH_'+var] = accu
639 process_lib(self.qt4_vars, 'LIBPATH_QTCORE')
640 process_lib(self.qt4_vars_debug, 'LIBPATH_QTCORE_DEBUG')
643 def add_qt4_rpath(self):
646 if getattr(Options.options, 'want_rpath', False):
647 def process_rpath(vars_, coreval):
650 value = env['LIBPATH_'+var]
658 accu.append('-Wl,--rpath='+lib)
659 env['RPATH_'+var] = accu
660 process_rpath(self.qt4_vars, 'LIBPATH_QTCORE')
661 process_rpath(self.qt4_vars_debug, 'LIBPATH_QTCORE_DEBUG')
664 def set_qt4_libs_to_check(self):
665 if not hasattr(self, 'qt4_vars'):
666 self.qt4_vars = QT4_LIBS
667 self.qt4_vars = Utils.to_list(self.qt4_vars)
668 if not hasattr(self, 'qt4_vars_debug'):
669 self.qt4_vars_debug = [a + '_debug' for a in self.qt4_vars]
670 self.qt4_vars_debug = Utils.to_list(self.qt4_vars_debug)
673 def set_qt4_defines(self):
674 if sys.platform != 'win32':
676 for x in self.qt4_vars:
678 self.env.append_unique('DEFINES_%s' % x.upper(), 'QT_%s_LIB' % y)
679 self.env.append_unique('DEFINES_%s_DEBUG' % x.upper(), 'QT_%s_LIB' % y)
685 opt.add_option('--want-rpath', action='store_true', default=False, dest='want_rpath', help='enable the rpath for qt libraries')
687 opt.add_option('--header-ext',
690 help='header extension for moc files',
691 dest='qt_header_ext')
693 for i in 'qtdir qtbin qtlibs'.split():
694 opt.add_option('--'+i, type='string', default='', dest=i)
696 opt.add_option('--translate', action="store_true", help="collect translation strings", dest="trans_qt4", default=False)