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 = sorted(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.append(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}'
247 class XMLHandler(ContentHandler):
249 Parser for *.qrc* files
254 def startElement(self, name, attrs):
257 def endElement(self, name):
259 self.files.append(str(''.join(self.buf)))
260 def characters(self, cars):
261 self.buf.append(cars)
264 def create_rcc_task(self, node):
265 "Create rcc and cxx tasks for *.qrc* files"
266 rcnode = node.change_ext('_rc.cpp')
267 self.create_task('rcc', node, rcnode)
268 cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o'))
270 self.compiled_tasks.append(cpptask)
271 except AttributeError:
272 self.compiled_tasks = [cpptask]
276 def create_uic_task(self, node):
278 uictask = self.create_task('ui4', node)
279 uictask.outputs = [self.path.find_or_declare(self.env['ui_PATTERN'] % node.name[:-3])]
282 def add_lang(self, node):
283 """add all the .ts file into self.lang"""
284 self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
287 @after_method('apply_link')
290 Add MOC_FLAGS which may be necessary for moc::
293 bld.program(features='qt4', source='main.cpp', target='app', use='QTCORE')
295 The additional parameters are:
297 :param lang: list of translation files (\*.ts) to process
298 :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
299 :param update: whether to process the C++ files to update the \*.ts files (use **waf --translate**)
301 :param langname: if given, transform the \*.ts files into a .qrc files to include in the binary file
302 :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
304 if getattr(self, 'lang', None):
306 for x in self.to_list(self.lang):
307 if isinstance(x, str):
308 x = self.path.find_resource(x + '.ts')
309 qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.qm')))
311 if getattr(self, 'update', None) and Options.options.trans_qt4:
312 cxxnodes = [a.inputs[0] for a in self.compiled_tasks] + [
313 a.inputs[0] for a in self.tasks if getattr(a, 'inputs', None) and a.inputs[0].name.endswith('.ui')]
315 self.create_task('trans_update', cxxnodes, x.inputs)
317 if getattr(self, 'langname', None):
318 qmnodes = [x.outputs[0] for x in qmtasks]
319 rcnode = self.langname
320 if isinstance(rcnode, str):
321 rcnode = self.path.find_or_declare(rcnode + '.qrc')
322 t = self.create_task('qm2rcc', qmnodes, rcnode)
323 k = create_rcc_task(self, t.outputs[0])
324 self.link_task.inputs.append(k.outputs[0])
327 for flag in self.to_list(self.env['CXXFLAGS']):
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)
380 return (nodes, names)
382 class moc(Task.Task):
387 run_str = '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
391 return self.outputs[0].path_from(self.generator.bld.launch_node())
393 class ui4(Task.Task):
398 run_str = '${QT_UIC} ${SRC} -o ${TGT}'
401 class ts2qm(Task.Task):
403 Create *.qm* files from *.ts* files
406 run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
408 class qm2rcc(Task.Task):
410 Transform *.qm* files into *.rc* files
416 """Create a qrc file including the inputs"""
417 txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
418 code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
419 self.outputs[0].write(code)
423 Besides the configuration options, the environment variable QT4_ROOT may be used
424 to give the location of the qt4 libraries (absolute path).
426 The detection will use the program *pkg-config* through :py:func:`waflib.Tools.config_c.check_cfg`
428 self.find_qt4_binaries()
429 self.set_qt4_libs_to_check()
430 self.set_qt4_defines()
431 self.find_qt4_libraries()
433 self.simplify_qt4_libs()
436 def find_qt4_binaries(self):
438 opt = Options.options
440 qtdir = getattr(opt, 'qtdir', '')
441 qtbin = getattr(opt, 'qtbin', '')
446 qtbin = os.path.join(qtdir, 'bin')
448 # the qt directory has been given from QT4_ROOT - deduce the qt binary path
450 qtdir = os.environ.get('QT4_ROOT', '')
451 qtbin = os.environ.get('QT4_BIN') or os.path.join(qtdir, 'bin')
456 # no qtdir, look in the path and in /usr/local/Trolltech
458 paths = os.environ.get('PATH', '').split(os.pathsep)
459 paths.append('/usr/share/qt4/bin/')
461 lst = Utils.listdir('/usr/local/Trolltech/')
469 # keep the highest version
470 qtdir = '/usr/local/Trolltech/%s/' % lst[0]
471 qtbin = os.path.join(qtdir, 'bin')
474 # at the end, try to find qmake in the paths given
475 # keep the one with the highest version
477 prev_ver = ['4', '0', '0']
478 for qmk in ('qmake-qt4', 'qmake4', 'qmake'):
480 qmake = self.find_program(qmk, path_list=paths)
481 except self.errors.ConfigurationError:
485 version = self.cmd_and_log(qmake + ['-query', 'QT_VERSION']).strip()
486 except self.errors.WafError:
490 new_ver = version.split('.')
491 if new_ver > prev_ver:
495 self.env.QMAKE = cand
497 self.fatal('Could not find qmake for qt4')
499 qtbin = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_BINS']).strip() + os.sep
501 def find_bin(lst, var):
506 ret = self.find_program(f, path_list=paths)
507 except self.errors.ConfigurationError:
513 find_bin(['uic-qt3', 'uic3'], 'QT_UIC3')
514 find_bin(['uic-qt4', 'uic'], 'QT_UIC')
516 self.fatal('cannot find the uic compiler for qt4')
518 self.start_msg('Checking for uic version')
519 uicver = self.cmd_and_log(env.QT_UIC + ["-version"], output=Context.BOTH)
520 uicver = ''.join(uicver).strip()
521 uicver = uicver.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
523 if uicver.find(' 3.') != -1:
524 self.fatal('this uic compiler is for qt3, add uic for qt4 to your path')
526 find_bin(['moc-qt4', 'moc'], 'QT_MOC')
527 find_bin(['rcc-qt4', 'rcc'], 'QT_RCC')
528 find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE')
529 find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE')
531 env['UIC3_ST']= '%s -o %s'
532 env['UIC_ST'] = '%s -o %s'
534 env['ui_PATTERN'] = 'ui_%s.h'
535 env['QT_LRELEASE_FLAGS'] = ['-silent']
536 env.MOCCPPPATH_ST = '-I%s'
537 env.MOCDEFINES_ST = '-D%s'
540 def find_qt4_libraries(self):
541 qtlibs = getattr(Options.options, 'qtlibs', None) or os.environ.get("QT4_LIBDIR")
544 qtlibs = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_LIBS']).strip()
545 except Errors.WafError:
546 qtdir = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_PREFIX']).strip() + os.sep
547 qtlibs = os.path.join(qtdir, 'lib')
548 self.msg('Found the Qt4 libraries in', qtlibs)
550 qtincludes = os.environ.get("QT4_INCLUDES") or self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_INSTALL_HEADERS']).strip()
552 if not 'PKG_CONFIG_PATH' in os.environ:
553 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)
556 if os.environ.get("QT4_XCOMPILE"):
557 raise self.errors.ConfigurationError()
558 self.check_cfg(atleast_pkgconfig_version='0.1')
559 except self.errors.ConfigurationError:
560 for i in self.qt4_vars:
562 if Utils.unversioned_sys_platform() == "darwin":
563 # Since at least qt 4.7.3 each library locates in separate directory
564 frameworkName = i + ".framework"
565 qtDynamicLib = os.path.join(qtlibs, frameworkName, i)
566 if os.path.exists(qtDynamicLib):
567 env.append_unique('FRAMEWORK_' + uselib, i)
568 self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
570 self.msg('Checking for %s' % i, False, 'YELLOW')
571 env.append_unique('INCLUDES_' + uselib, os.path.join(qtlibs, frameworkName, 'Headers'))
572 elif env.DEST_OS != "win32":
573 qtDynamicLib = os.path.join(qtlibs, "lib" + i + ".so")
574 qtStaticLib = os.path.join(qtlibs, "lib" + i + ".a")
575 if os.path.exists(qtDynamicLib):
576 env.append_unique('LIB_' + uselib, i)
577 self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
578 elif os.path.exists(qtStaticLib):
579 env.append_unique('LIB_' + uselib, i)
580 self.msg('Checking for %s' % i, qtStaticLib, 'GREEN')
582 self.msg('Checking for %s' % i, False, 'YELLOW')
584 env.append_unique('LIBPATH_' + uselib, qtlibs)
585 env.append_unique('INCLUDES_' + uselib, qtincludes)
586 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
588 # Release library names are like QtCore4
589 for k in ("lib%s.a", "lib%s4.a", "%s.lib", "%s4.lib"):
590 lib = os.path.join(qtlibs, k % i)
591 if os.path.exists(lib):
592 env.append_unique('LIB_' + uselib, i + k[k.find("%s") + 2 : k.find('.')])
593 self.msg('Checking for %s' % i, lib, 'GREEN')
596 self.msg('Checking for %s' % i, False, 'YELLOW')
598 env.append_unique('LIBPATH_' + uselib, qtlibs)
599 env.append_unique('INCLUDES_' + uselib, qtincludes)
600 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
602 # Debug library names are like QtCore4d
603 uselib = i.upper() + "_debug"
604 for k in ("lib%sd.a", "lib%sd4.a", "%sd.lib", "%sd4.lib"):
605 lib = os.path.join(qtlibs, k % i)
606 if os.path.exists(lib):
607 env.append_unique('LIB_' + uselib, i + k[k.find("%s") + 2 : k.find('.')])
608 self.msg('Checking for %s' % i, lib, 'GREEN')
611 self.msg('Checking for %s' % i, False, 'YELLOW')
613 env.append_unique('LIBPATH_' + uselib, qtlibs)
614 env.append_unique('INCLUDES_' + uselib, qtincludes)
615 env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, i))
617 for i in self.qt4_vars_debug + self.qt4_vars:
618 self.check_cfg(package=i, args='--cflags --libs', mandatory=False)
621 def simplify_qt4_libs(self):
622 # the libpaths make really long command-lines
623 # remove the qtcore ones from qtgui, etc
625 def process_lib(vars_, coreval):
631 value = env['LIBPATH_'+var]
639 env['LIBPATH_'+var] = accu
641 process_lib(self.qt4_vars, 'LIBPATH_QTCORE')
642 process_lib(self.qt4_vars_debug, 'LIBPATH_QTCORE_DEBUG')
645 def add_qt4_rpath(self):
648 if getattr(Options.options, 'want_rpath', False):
649 def process_rpath(vars_, coreval):
652 value = env['LIBPATH_'+var]
660 accu.append('-Wl,--rpath='+lib)
661 env['RPATH_'+var] = accu
662 process_rpath(self.qt4_vars, 'LIBPATH_QTCORE')
663 process_rpath(self.qt4_vars_debug, 'LIBPATH_QTCORE_DEBUG')
666 def set_qt4_libs_to_check(self):
667 if not hasattr(self, 'qt4_vars'):
668 self.qt4_vars = QT4_LIBS
669 self.qt4_vars = Utils.to_list(self.qt4_vars)
670 if not hasattr(self, 'qt4_vars_debug'):
671 self.qt4_vars_debug = [a + '_debug' for a in self.qt4_vars]
672 self.qt4_vars_debug = Utils.to_list(self.qt4_vars_debug)
675 def set_qt4_defines(self):
676 if sys.platform != 'win32':
678 for x in self.qt4_vars:
680 self.env.append_unique('DEFINES_%s' % x.upper(), 'QT_%s_LIB' % y)
681 self.env.append_unique('DEFINES_%s_DEBUG' % x.upper(), 'QT_%s_LIB' % y)
687 opt.add_option('--want-rpath', action='store_true', default=False, dest='want_rpath', help='enable the rpath for qt libraries')
689 opt.add_option('--header-ext',
692 help='header extension for moc files',
693 dest='qt_header_ext')
695 for i in 'qtdir qtbin qtlibs'.split():
696 opt.add_option('--'+i, type='string', default='', dest=i)
698 opt.add_option('--translate', action="store_true", help="collect translation strings", dest="trans_qt4", default=False)