waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

qt5.py (24552B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2006-2018 (ita)
      4 
      5 """
      6 This tool helps with finding Qt5 tools and libraries,
      7 and also provides syntactic sugar for using Qt5 tools.
      8 
      9 The 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 
     25 Here, the UI description and resource files will be processed
     26 to generate code.
     27 
     28 Usage
     29 =====
     30 
     31 Load the "qt5" tool.
     32 
     33 You 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 
     53 Note: another tool provides Qt processing that does not require
     54 .moc includes, see 'playground/slow_qt/'.
     55 
     56 A few options (--qt{dir,bin,...}) and environment variables
     57 (QT5_{ROOT,DIR,MOC,UIC,XCOMPILE}) allow finer tuning of the tool,
     58 tool path selection, etc; please read the source for more info.
     59 
     60 The detection uses pkg-config on Linux by default. The list of
     61 libraries to be requested to pkg-config is formulated by scanning
     62 in the QTLIBS directory (that can be passed via --qtlibs or by
     63 setting the environment variable QT5_LIBDIR otherwise is derived
     64 by querying qmake for QT_INSTALL_LIBS directory) for shared/static
     65 libraries present.
     66 Alternatively the list of libraries to be requested via pkg-config
     67 can be set using the qt5_vars attribute, ie:
     68 
     69       conf.qt5_vars = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Test'];
     70 
     71 This can speed up configuration phase if needed libraries are
     72 known beforehand, can improve detection on systems with a
     73 sparse QT5 libraries installation (ie. NIX) and can improve
     74 detection of some header-only Qt modules (ie. Qt5UiPlugin).
     75 
     76 To force static library detection use:
     77 QT5_XCOMPILE=1 QT5_FORCE_STATIC=1 waf configure
     78 """
     79 
     80 from __future__ import with_statement
     81 
     82 try:
     83 	from xml.sax import make_parser
     84 	from xml.sax.handler import ContentHandler
     85 except ImportError:
     86 	has_xml = False
     87 	ContentHandler = object
     88 else:
     89 	has_xml = True
     90 
     91 import os, sys, re
     92 from waflib.Tools import cxx
     93 from waflib import Build, Task, Utils, Options, Errors, Context
     94 from waflib.TaskGen import feature, after_method, extension, before_method
     95 from waflib.Configure import conf
     96 from waflib import Logs
     97 
     98 MOC_H = ['.h', '.hpp', '.hxx', '.hh']
     99 """
    100 File extensions associated to .moc files
    101 """
    102 
    103 EXT_RCC = ['.qrc']
    104 """
    105 File extension for the resource (.qrc) files
    106 """
    107 
    108 EXT_UI  = ['.ui']
    109 """
    110 File extension for the user interface (.ui) files
    111 """
    112 
    113 EXT_QT5 = ['.cpp', '.cc', '.cxx', '.C']
    114 """
    115 File extensions of C++ files that may require a .moc processing
    116 """
    117 
    118 class 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:
    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 
    245 class 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 
    250 class 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)
    268 def 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)
    280 def 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')
    299 def 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')
    305 def 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')
    327 def 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)
    378 def 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 
    384 class 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 
    431 class 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 
    451 class 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 
    459 class 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 
    466 class 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 
    478 def 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'
    503 	for flag in [[], '-fPIE', '-fPIC', '-std=c++11' , ['-std=c++11', '-fPIE'], ['-std=c++11', '-fPIC']]:
    504 		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
    525 def 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 = ['0', '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[0] == '5' and 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
    647 def 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
    660 def 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
    691 def 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
    728 def 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
    752 def 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
    774 def 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>\\w+)' + '$')
    787 		for x in sorted(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
    799 def 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 
    806 def 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