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