3 # Thomas Nagy, 2006-2010 (ita)
10 This tool helps with finding Qt4 tools and libraries,
11 and also provides syntactic sugar for using Qt4 tools.
13 The following snippet illustrates the tool usage::
16 opt.load('compiler_cxx qt4')
19 conf.load('compiler_cxx qt4')
23 features = 'qt4 cxx cxxprogram',
24 uselib = 'QTCORE QTGUI QTOPENGL QTSVG',
25 source = 'main.cpp textures.qrc aboutDialog.ui',
29 Here, the UI description and resource files will be processed
37 You also need to edit your sources accordingly:
39 - the normal way of doing things is to have your C++ files
40 include the .moc file.
41 This is regarded as the best practice (and provides much faster
43 It also implies that the include paths have beenset properly.
45 - to have the include paths added automatically, use the following::
47 from waflib.TaskGen import feature, before_method, after_method
49 @after_method('process_source')
50 @before_method('apply_incpaths')
51 def add_includes_paths(self):
52 incs = set(self.to_list(getattr(self, 'includes', '')))
53 for x in self.compiled_tasks:
54 incs.add(x.inputs[0].parent.path_from(self.path))
55 self.includes = sorted(incs)
57 Note: another tool provides Qt processing that does not require
58 .moc includes, see 'playground/slow_qt/'.
60 A few options (--qt{dir,bin,...}) and environment variables
61 (QT4_{ROOT,DIR,MOC,UIC,XCOMPILE}) allow finer tuning of the tool,
62 tool path selection, etc; please read the source for more info.
67 from xml
.sax
import make_parser
68 from xml
.sax
.handler
import ContentHandler
71 ContentHandler
= object
76 from waflib
.Tools
import cxx
77 from waflib
import Task
, Utils
, Options
, Errors
, Context
78 from waflib
.TaskGen
import feature
, after_method
, extension
79 from waflib
.Configure
import conf
80 from waflib
import Logs
82 MOC_H
= ['.h', '.hpp', '.hxx', '.hh']
84 File extensions associated to the .moc files
89 File extension for the resource (.qrc) files
94 File extension for the user interface (.ui) files
97 EXT_QT4
= ['.cpp', '.cc', '.cxx', '.C']
99 File extensions of C++ files that may require a .moc processing
102 QT4_LIBS
= "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtXmlPatterns QtWebKit Qt3Support QtHelp QtScript QtDeclarative QtDesigner"
104 class qxx(Task
.classes
['cxx']):
106 Each C++ file can have zero or several .moc files to create.
107 They are known only when the files are scanned (preprocessor)
108 To avoid scanning the c++ files each time (parsing C/C++), the results
109 are retrieved from the task cache (bld.node_deps/bld.raw_deps).
110 The moc tasks are also created *dynamically* during the build.
113 def __init__(self
, *k
, **kw
):
114 Task
.Task
.__init
__(self
, *k
, **kw
)
117 def runnable_status(self
):
119 Compute the task signature to make sure the scanner was executed. Create the
120 moc tasks by using :py:meth:`waflib.Tools.qt4.qxx.add_moc_tasks` (if necessary),
121 then postpone the task execution (there is no need to recompute the task signature).
124 return Task
.Task
.runnable_status(self
)
126 for t
in self
.run_after
:
128 return Task
.ASK_LATER
130 return Task
.Task
.runnable_status(self
)
132 def create_moc_task(self
, h_node
, m_node
):
134 If several libraries use the same classes, it is possible that moc will run several times (Issue 1318)
135 It is not possible to change the file names, but we can assume that the moc transformation will be identical,
136 and the moc tasks can be shared in a global cache.
138 The defines passed to moc will then depend on task generator order. If this is not acceptable, then
139 use the tool slow_qt4 instead (and enjoy the slow builds... :-( )
142 moc_cache
= self
.generator
.bld
.moc_cache
143 except AttributeError:
144 moc_cache
= self
.generator
.bld
.moc_cache
= {}
147 return moc_cache
[h_node
]
149 tsk
= moc_cache
[h_node
] = Task
.classes
['moc'](env
=self
.env
, generator
=self
.generator
)
150 tsk
.set_inputs(h_node
)
151 tsk
.set_outputs(m_node
)
154 self
.generator
.tasks
.append(tsk
)
156 # direct injection in the build phase (safe because called from the main thread)
157 gen
= self
.generator
.bld
.producer
158 gen
.outstanding
.append(tsk
)
166 ext
= Options
.options
.qt_header_ext
.split()
167 except AttributeError:
173 def add_moc_tasks(self
):
175 Create the moc tasks by looking in ``bld.raw_deps[self.uid()]``
177 node
= self
.inputs
[0]
178 bld
= self
.generator
.bld
181 # compute the signature once to know if there is a moc file to create
184 # the moc file may be referenced somewhere else
187 # remove the signature, it must be recomputed with the moc task
188 delattr(self
, 'cache_sig')
190 include_nodes
= [node
.parent
] + self
.generator
.includes_nodes
194 for d
in bld
.raw_deps
.get(self
.uid(), []):
195 if not d
.endswith('.moc'):
198 # process that base.moc only once
203 # find the source associated with the moc file
207 for x
in include_nodes
:
208 for e
in self
.moc_h_ext():
209 h_node
= x
.find_node(base2
+ e
)
213 m_node
= h_node
.change_ext('.moc')
216 # foo.cpp -> foo.cpp.moc
218 if base2
.endswith(k
):
219 for x
in include_nodes
:
220 h_node
= x
.find_node(base2
)
224 m_node
= h_node
.change_ext(k
+ '.moc')
228 raise Errors
.WafError('No source found for %r which is a moc file' % d
)
230 # create the moc task
231 task
= self
.create_moc_task(h_node
, m_node
)
232 moctasks
.append(task
)
234 # simple scheduler dependency: run the moc task before others
235 self
.run_after
.update(set(moctasks
))
238 class trans_update(Task
.Task
):
239 """Update a .ts files from a list of C++ files"""
240 run_str
= '${QT_LUPDATE} ${SRC} -ts ${TGT}'
243 class XMLHandler(ContentHandler
):
245 Parser for *.qrc* files
250 def startElement(self
, name
, attrs
):
253 def endElement(self
, name
):
255 self
.files
.append(str(''.join(self
.buf
)))
256 def characters(self
, cars
):
257 self
.buf
.append(cars
)
260 def create_rcc_task(self
, node
):
261 "Create rcc and cxx tasks for *.qrc* files"
262 rcnode
= node
.change_ext('_rc.cpp')
263 self
.create_task('rcc', node
, rcnode
)
264 cpptask
= self
.create_task('cxx', rcnode
, rcnode
.change_ext('.o'))
266 self
.compiled_tasks
.append(cpptask
)
267 except AttributeError:
268 self
.compiled_tasks
= [cpptask
]
272 def create_uic_task(self
, node
):
274 uictask
= self
.create_task('ui4', node
)
275 uictask
.outputs
= [self
.path
.find_or_declare(self
.env
['ui_PATTERN'] % node
.name
[:-3])]
278 def add_lang(self
, node
):
279 """add all the .ts file into self.lang"""
280 self
.lang
= self
.to_list(getattr(self
, 'lang', [])) + [node
]
283 @after_method('apply_link')
286 Add MOC_FLAGS which may be necessary for moc::
289 bld.program(features='qt4', source='main.cpp', target='app', use='QTCORE')
291 The additional parameters are:
293 :param lang: list of translation files (\\*.ts) to process
294 :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
295 :param update: whether to process the C++ files to update the \\*.ts files (use **waf --translate**)
297 :param langname: if given, transform the \\*.ts files into a .qrc files to include in the binary file
298 :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
300 if getattr(self
, 'lang', None):
302 for x
in self
.to_list(self
.lang
):
303 if isinstance(x
, str):
304 x
= self
.path
.find_resource(x
+ '.ts')
305 qmtasks
.append(self
.create_task('ts2qm', x
, x
.change_ext('.qm')))
307 if getattr(self
, 'update', None) and Options
.options
.trans_qt4
:
308 cxxnodes
= [a
.inputs
[0] for a
in self
.compiled_tasks
] + [
309 a
.inputs
[0] for a
in self
.tasks
if getattr(a
, 'inputs', None) and a
.inputs
[0].name
.endswith('.ui')]
311 self
.create_task('trans_update', cxxnodes
, x
.inputs
)
313 if getattr(self
, 'langname', None):
314 qmnodes
= [x
.outputs
[0] for x
in qmtasks
]
315 rcnode
= self
.langname
316 if isinstance(rcnode
, str):
317 rcnode
= self
.path
.find_or_declare(rcnode
+ '.qrc')
318 t
= self
.create_task('qm2rcc', qmnodes
, rcnode
)
319 k
= create_rcc_task(self
, t
.outputs
[0])
320 self
.link_task
.inputs
.append(k
.outputs
[0])
323 for flag
in self
.to_list(self
.env
['CXXFLAGS']):
327 if f
in ('-D', '-I', '/D', '/I'):
329 lst
.append('-' + flag
[1:])
332 self
.env
.append_value('MOC_FLAGS', lst
)
335 def cxx_hook(self
, node
):
337 Re-map C++ file extensions to the :py:class:`waflib.Tools.qt4.qxx` task.
339 return self
.create_compiled_task('qxx', node
)
341 class rcc(Task
.Task
):
346 run_str
= '${QT_RCC} -name ${tsk.rcname()} ${SRC[0].abspath()} ${RCC_ST} -o ${TGT}'
350 return os
.path
.splitext(self
.inputs
[0].name
)[0]
353 """Parse the *.qrc* files"""
355 Logs
.error('no xml support was found, the rcc dependencies will be incomplete!')
358 parser
= make_parser()
359 curHandler
= XMLHandler()
360 parser
.setContentHandler(curHandler
)
361 fi
= open(self
.inputs
[0].abspath(), 'r')
369 root
= self
.inputs
[0].parent
370 for x
in curHandler
.files
:
371 nd
= root
.find_resource(x
)
376 return (nodes
, names
)
378 class moc(Task
.Task
):
383 run_str
= '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
387 return self
.outputs
[0].path_from(self
.generator
.bld
.launch_node())
389 class ui4(Task
.Task
):
394 run_str
= '${QT_UIC} ${SRC} -o ${TGT}'
397 class ts2qm(Task
.Task
):
399 Create *.qm* files from *.ts* files
402 run_str
= '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
404 class qm2rcc(Task
.Task
):
406 Transform *.qm* files into *.rc* files
412 """Create a qrc file including the inputs"""
413 txt
= '\n'.join(['<file>%s</file>' % k
.path_from(self
.outputs
[0].parent
) for k
in self
.inputs
])
414 code
= '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
415 self
.outputs
[0].write(code
)
419 Besides the configuration options, the environment variable QT4_ROOT may be used
420 to give the location of the qt4 libraries (absolute path).
422 The detection will use the program *pkg-config* through :py:func:`waflib.Tools.config_c.check_cfg`
424 self
.find_qt4_binaries()
425 self
.set_qt4_libs_to_check()
426 self
.set_qt4_defines()
427 self
.find_qt4_libraries()
429 self
.simplify_qt4_libs()
432 def find_qt4_binaries(self
):
434 opt
= Options
.options
436 qtdir
= getattr(opt
, 'qtdir', '')
437 qtbin
= getattr(opt
, 'qtbin', '')
442 qtbin
= os
.path
.join(qtdir
, 'bin')
444 # the qt directory has been given from QT4_ROOT - deduce the qt binary path
446 qtdir
= os
.environ
.get('QT4_ROOT', '')
447 qtbin
= os
.environ
.get('QT4_BIN') or os
.path
.join(qtdir
, 'bin')
452 # no qtdir, look in the path and in /usr/local/Trolltech
454 paths
= os
.environ
.get('PATH', '').split(os
.pathsep
)
455 paths
.append('/usr/share/qt4/bin/')
457 lst
= Utils
.listdir('/usr/local/Trolltech/')
465 # keep the highest version
466 qtdir
= '/usr/local/Trolltech/%s/' % lst
[0]
467 qtbin
= os
.path
.join(qtdir
, 'bin')
470 # at the end, try to find qmake in the paths given
471 # keep the one with the highest version
473 prev_ver
= ['4', '0', '0']
474 for qmk
in ('qmake-qt4', 'qmake4', 'qmake'):
476 qmake
= self
.find_program(qmk
, path_list
=paths
)
477 except self
.errors
.ConfigurationError
:
481 version
= self
.cmd_and_log(qmake
+ ['-query', 'QT_VERSION']).strip()
482 except self
.errors
.WafError
:
486 new_ver
= version
.split('.')
487 if new_ver
> prev_ver
:
491 self
.env
.QMAKE
= cand
493 self
.fatal('Could not find qmake for qt4')
495 qtbin
= self
.cmd_and_log(self
.env
.QMAKE
+ ['-query', 'QT_INSTALL_BINS']).strip() + os
.sep
497 def find_bin(lst
, var
):
502 ret
= self
.find_program(f
, path_list
=paths
)
503 except self
.errors
.ConfigurationError
:
509 find_bin(['uic-qt3', 'uic3'], 'QT_UIC3')
510 find_bin(['uic-qt4', 'uic'], 'QT_UIC')
512 self
.fatal('cannot find the uic compiler for qt4')
514 self
.start_msg('Checking for uic version')
515 uicver
= self
.cmd_and_log(env
.QT_UIC
+ ["-version"], output
=Context
.BOTH
)
516 uicver
= ''.join(uicver
).strip()
517 uicver
= uicver
.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
519 if uicver
.find(' 3.') != -1:
520 self
.fatal('this uic compiler is for qt3, add uic for qt4 to your path')
522 find_bin(['moc-qt4', 'moc'], 'QT_MOC')
523 find_bin(['rcc-qt4', 'rcc'], 'QT_RCC')
524 find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE')
525 find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE')
527 env
['UIC3_ST']= '%s -o %s'
528 env
['UIC_ST'] = '%s -o %s'
530 env
['ui_PATTERN'] = 'ui_%s.h'
531 env
['QT_LRELEASE_FLAGS'] = ['-silent']
532 env
.MOCCPPPATH_ST
= '-I%s'
533 env
.MOCDEFINES_ST
= '-D%s'
536 def find_qt4_libraries(self
):
537 qtlibs
= getattr(Options
.options
, 'qtlibs', None) or os
.environ
.get("QT4_LIBDIR")
540 qtlibs
= self
.cmd_and_log(self
.env
.QMAKE
+ ['-query', 'QT_INSTALL_LIBS']).strip()
541 except Errors
.WafError
:
542 qtdir
= self
.cmd_and_log(self
.env
.QMAKE
+ ['-query', 'QT_INSTALL_PREFIX']).strip() + os
.sep
543 qtlibs
= os
.path
.join(qtdir
, 'lib')
544 self
.msg('Found the Qt4 libraries in', qtlibs
)
546 qtincludes
= os
.environ
.get("QT4_INCLUDES") or self
.cmd_and_log(self
.env
.QMAKE
+ ['-query', 'QT_INSTALL_HEADERS']).strip()
548 if not 'PKG_CONFIG_PATH' in os
.environ
:
549 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
)
552 if os
.environ
.get("QT4_XCOMPILE"):
553 raise self
.errors
.ConfigurationError()
554 self
.check_cfg(atleast_pkgconfig_version
='0.1')
555 except self
.errors
.ConfigurationError
:
556 for i
in self
.qt4_vars
:
558 if Utils
.unversioned_sys_platform() == "darwin":
559 # Since at least qt 4.7.3 each library locates in separate directory
560 frameworkName
= i
+ ".framework"
561 qtDynamicLib
= os
.path
.join(qtlibs
, frameworkName
, i
)
562 if os
.path
.exists(qtDynamicLib
):
563 env
.append_unique('FRAMEWORK_' + uselib
, i
)
564 self
.msg('Checking for %s' % i
, qtDynamicLib
, 'GREEN')
566 self
.msg('Checking for %s' % i
, False, 'YELLOW')
567 env
.append_unique('INCLUDES_' + uselib
, os
.path
.join(qtlibs
, frameworkName
, 'Headers'))
568 elif env
.DEST_OS
!= "win32":
569 qtDynamicLib
= os
.path
.join(qtlibs
, "lib" + i
+ ".so")
570 qtStaticLib
= os
.path
.join(qtlibs
, "lib" + i
+ ".a")
571 if os
.path
.exists(qtDynamicLib
):
572 env
.append_unique('LIB_' + uselib
, i
)
573 self
.msg('Checking for %s' % i
, qtDynamicLib
, 'GREEN')
574 elif os
.path
.exists(qtStaticLib
):
575 env
.append_unique('LIB_' + uselib
, i
)
576 self
.msg('Checking for %s' % i
, qtStaticLib
, 'GREEN')
578 self
.msg('Checking for %s' % i
, False, 'YELLOW')
580 env
.append_unique('LIBPATH_' + uselib
, qtlibs
)
581 env
.append_unique('INCLUDES_' + uselib
, qtincludes
)
582 env
.append_unique('INCLUDES_' + uselib
, os
.path
.join(qtincludes
, i
))
584 # Release library names are like QtCore4
585 for k
in ("lib%s.a", "lib%s4.a", "%s.lib", "%s4.lib"):
586 lib
= os
.path
.join(qtlibs
, k
% i
)
587 if os
.path
.exists(lib
):
588 env
.append_unique('LIB_' + uselib
, i
+ k
[k
.find("%s") + 2 : k
.find('.')])
589 self
.msg('Checking for %s' % i
, lib
, 'GREEN')
592 self
.msg('Checking for %s' % i
, False, 'YELLOW')
594 env
.append_unique('LIBPATH_' + uselib
, qtlibs
)
595 env
.append_unique('INCLUDES_' + uselib
, qtincludes
)
596 env
.append_unique('INCLUDES_' + uselib
, os
.path
.join(qtincludes
, i
))
598 # Debug library names are like QtCore4d
599 uselib
= i
.upper() + "_debug"
600 for k
in ("lib%sd.a", "lib%sd4.a", "%sd.lib", "%sd4.lib"):
601 lib
= os
.path
.join(qtlibs
, k
% i
)
602 if os
.path
.exists(lib
):
603 env
.append_unique('LIB_' + uselib
, i
+ k
[k
.find("%s") + 2 : k
.find('.')])
604 self
.msg('Checking for %s' % i
, lib
, 'GREEN')
607 self
.msg('Checking for %s' % i
, False, 'YELLOW')
609 env
.append_unique('LIBPATH_' + uselib
, qtlibs
)
610 env
.append_unique('INCLUDES_' + uselib
, qtincludes
)
611 env
.append_unique('INCLUDES_' + uselib
, os
.path
.join(qtincludes
, i
))
613 for i
in self
.qt4_vars_debug
+ self
.qt4_vars
:
614 self
.check_cfg(package
=i
, args
='--cflags --libs', mandatory
=False)
617 def simplify_qt4_libs(self
):
618 # the libpaths make really long command-lines
619 # remove the qtcore ones from qtgui, etc
621 def process_lib(vars_
, coreval
):
627 value
= env
['LIBPATH_'+var
]
635 env
['LIBPATH_'+var
] = accu
637 process_lib(self
.qt4_vars
, 'LIBPATH_QTCORE')
638 process_lib(self
.qt4_vars_debug
, 'LIBPATH_QTCORE_DEBUG')
641 def add_qt4_rpath(self
):
644 if getattr(Options
.options
, 'want_rpath', False):
645 def process_rpath(vars_
, coreval
):
648 value
= env
['LIBPATH_'+var
]
656 accu
.append('-Wl,--rpath='+lib
)
657 env
['RPATH_'+var
] = accu
658 process_rpath(self
.qt4_vars
, 'LIBPATH_QTCORE')
659 process_rpath(self
.qt4_vars_debug
, 'LIBPATH_QTCORE_DEBUG')
662 def set_qt4_libs_to_check(self
):
663 if not hasattr(self
, 'qt4_vars'):
664 self
.qt4_vars
= QT4_LIBS
665 self
.qt4_vars
= Utils
.to_list(self
.qt4_vars
)
666 if not hasattr(self
, 'qt4_vars_debug'):
667 self
.qt4_vars_debug
= [a
+ '_debug' for a
in self
.qt4_vars
]
668 self
.qt4_vars_debug
= Utils
.to_list(self
.qt4_vars_debug
)
671 def set_qt4_defines(self
):
672 if sys
.platform
!= 'win32':
674 for x
in self
.qt4_vars
:
676 self
.env
.append_unique('DEFINES_%s' % x
.upper(), 'QT_%s_LIB' % y
)
677 self
.env
.append_unique('DEFINES_%s_DEBUG' % x
.upper(), 'QT_%s_LIB' % y
)
683 opt
.add_option('--want-rpath', action
='store_true', default
=False, dest
='want_rpath', help='enable the rpath for qt libraries')
685 opt
.add_option('--header-ext',
688 help='header extension for moc files',
689 dest
='qt_header_ext')
691 for i
in 'qtdir qtbin qtlibs'.split():
692 opt
.add_option('--'+i
, type='string', default
='', dest
=i
)
694 opt
.add_option('--translate', action
="store_true", help="collect translation strings", dest
="trans_qt4", default
=False)