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. To force static library detection use:
61QT5_XCOMPILE=1 QT5_FORCE_STATIC=1 waf configure
62"""
63
64from __future__ import with_statement
65
66try:
67	from xml.sax import make_parser
68	from xml.sax.handler import ContentHandler
69except ImportError:
70	has_xml = False
71	ContentHandler = object
72else:
73	has_xml = True
74
75import os, sys, re
76from waflib.Tools import cxx
77from waflib import Build, Task, Utils, Options, Errors, Context
78from waflib.TaskGen import feature, after_method, extension, before_method
79from waflib.Configure import conf
80from waflib import Logs
81
82MOC_H = ['.h', '.hpp', '.hxx', '.hh']
83"""
84File extensions associated to .moc files
85"""
86
87EXT_RCC = ['.qrc']
88"""
89File extension for the resource (.qrc) files
90"""
91
92EXT_UI  = ['.ui']
93"""
94File extension for the user interface (.ui) files
95"""
96
97EXT_QT5 = ['.cpp', '.cc', '.cxx', '.C']
98"""
99File extensions of C++ files that may require a .moc processing
100"""
101
102class qxx(Task.classes['cxx']):
103	"""
104	Each C++ file can have zero or several .moc files to create.
105	They are known only when the files are scanned (preprocessor)
106	To avoid scanning the c++ files each time (parsing C/C++), the results
107	are retrieved from the task cache (bld.node_deps/bld.raw_deps).
108	The moc tasks are also created *dynamically* during the build.
109	"""
110
111	def __init__(self, *k, **kw):
112		Task.Task.__init__(self, *k, **kw)
113		self.moc_done = 0
114
115	def runnable_status(self):
116		"""
117		Compute the task signature to make sure the scanner was executed. Create the
118		moc tasks by using :py:meth:`waflib.Tools.qt5.qxx.add_moc_tasks` (if necessary),
119		then postpone the task execution (there is no need to recompute the task signature).
120		"""
121		if self.moc_done:
122			return Task.Task.runnable_status(self)
123		else:
124			for t in self.run_after:
125				if not t.hasrun:
126					return Task.ASK_LATER
127			self.add_moc_tasks()
128			return Task.Task.runnable_status(self)
129
130	def create_moc_task(self, h_node, m_node):
131		"""
132		If several libraries use the same classes, it is possible that moc will run several times (Issue 1318)
133		It is not possible to change the file names, but we can assume that the moc transformation will be identical,
134		and the moc tasks can be shared in a global cache.
135		"""
136		try:
137			moc_cache = self.generator.bld.moc_cache
138		except AttributeError:
139			moc_cache = self.generator.bld.moc_cache = {}
140
141		try:
142			return moc_cache[h_node]
143		except KeyError:
144			tsk = moc_cache[h_node] = Task.classes['moc'](env=self.env, generator=self.generator)
145			tsk.set_inputs(h_node)
146			tsk.set_outputs(m_node)
147			tsk.env.append_unique('MOC_FLAGS', '-i')
148
149			if self.generator:
150				self.generator.tasks.append(tsk)
151
152			# direct injection in the build phase (safe because called from the main thread)
153			gen = self.generator.bld.producer
154			gen.outstanding.append(tsk)
155			gen.total += 1
156
157			return tsk
158
159		else:
160			# remove the signature, it must be recomputed with the moc task
161			delattr(self, 'cache_sig')
162
163	def add_moc_tasks(self):
164		"""
165		Creates moc tasks by looking in the list of file dependencies ``bld.raw_deps[self.uid()]``
166		"""
167		node = self.inputs[0]
168		bld = self.generator.bld
169
170		# skip on uninstall due to generated files
171		if bld.is_install == Build.UNINSTALL:
172			return
173
174		try:
175			# compute the signature once to know if there is a moc file to create
176			self.signature()
177		except KeyError:
178			# the moc file may be referenced somewhere else
179			pass
180		else:
181			# remove the signature, it must be recomputed with the moc task
182			delattr(self, 'cache_sig')
183
184		include_nodes = [node.parent] + self.generator.includes_nodes
185
186		moctasks = []
187		mocfiles = set()
188		for d in bld.raw_deps.get(self.uid(), []):
189			if not d.endswith('.moc'):
190				continue
191
192			# process that base.moc only once
193			if d in mocfiles:
194				continue
195			mocfiles.add(d)
196
197			# find the source associated with the moc file
198			h_node = None
199			base2 = d[:-4]
200
201			# foo.moc from foo.cpp
202			prefix = node.name[:node.name.rfind('.')]
203			if base2 == prefix:
204				h_node = node
205			else:
206				# this deviates from the standard
207				# if bar.cpp includes foo.moc, then assume it is from foo.h
208				for x in include_nodes:
209					for e in MOC_H:
210						h_node = x.find_node(base2 + e)
211						if h_node:
212							break
213					else:
214						continue
215					break
216			if h_node:
217				m_node = h_node.change_ext('.moc')
218			else:
219				raise Errors.WafError('No source found for %r which is a moc file' % d)
220
221			# create the moc task
222			task = self.create_moc_task(h_node, m_node)
223			moctasks.append(task)
224
225		# simple scheduler dependency: run the moc task before others
226		self.run_after.update(set(moctasks))
227		self.moc_done = 1
228
229class trans_update(Task.Task):
230	"""Updates a .ts files from a list of C++ files"""
231	run_str = '${QT_LUPDATE} ${SRC} -ts ${TGT}'
232	color   = 'BLUE'
233
234class XMLHandler(ContentHandler):
235	"""
236	Parses ``.qrc`` files
237	"""
238	def __init__(self):
239		ContentHandler.__init__(self)
240		self.buf = []
241		self.files = []
242	def startElement(self, name, attrs):
243		if name == 'file':
244			self.buf = []
245	def endElement(self, name):
246		if name == 'file':
247			self.files.append(str(''.join(self.buf)))
248	def characters(self, cars):
249		self.buf.append(cars)
250
251@extension(*EXT_RCC)
252def create_rcc_task(self, node):
253	"Creates rcc and cxx tasks for ``.qrc`` files"
254	rcnode = node.change_ext('_rc.%d.cpp' % self.idx)
255	self.create_task('rcc', node, rcnode)
256	cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o'))
257	try:
258		self.compiled_tasks.append(cpptask)
259	except AttributeError:
260		self.compiled_tasks = [cpptask]
261	return cpptask
262
263@extension(*EXT_UI)
264def create_uic_task(self, node):
265	"Create uic tasks for user interface ``.ui`` definition files"
266
267	"""
268	If UIC file is used in more than one bld, we would have a conflict in parallel execution
269	It is not possible to change the file names (like .self.idx. as for objects) as they have
270	to be referenced by the source file, but we can assume that the transformation will be identical
271	and the tasks can be shared in a global cache.
272	"""
273	try:
274		uic_cache = self.bld.uic_cache
275	except AttributeError:
276		uic_cache = self.bld.uic_cache = {}
277
278	if node not in uic_cache:
279		uictask = uic_cache[node] = self.create_task('ui5', node)
280		uictask.outputs = [node.parent.find_or_declare(self.env.ui_PATTERN % node.name[:-3])]
281
282@extension('.ts')
283def add_lang(self, node):
284	"""Adds all the .ts file into ``self.lang``"""
285	self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
286
287@feature('qt5')
288@before_method('process_source')
289def process_mocs(self):
290	"""
291	Processes MOC files included in headers::
292
293		def build(bld):
294			bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE', moc='foo.h')
295
296	The build will run moc on foo.h to create moc_foo.n.cpp. The number in the file name
297	is provided to avoid name clashes when the same headers are used by several targets.
298	"""
299	lst = self.to_nodes(getattr(self, 'moc', []))
300	self.source = self.to_list(getattr(self, 'source', []))
301	for x in lst:
302		prefix = x.name[:x.name.rfind('.')] # foo.h -> foo
303		moc_target = 'moc_%s.%d.cpp' % (prefix, self.idx)
304		moc_node = x.parent.find_or_declare(moc_target)
305		self.source.append(moc_node)
306
307		self.create_task('moc', x, moc_node)
308
309@feature('qt5')
310@after_method('apply_link')
311def apply_qt5(self):
312	"""
313	Adds MOC_FLAGS which may be necessary for moc::
314
315		def build(bld):
316			bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE')
317
318	The additional parameters are:
319
320	:param lang: list of translation files (\\*.ts) to process
321	:type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
322	:param update: whether to process the C++ files to update the \\*.ts files (use **waf --translate**)
323	:type update: bool
324	:param langname: if given, transform the \\*.ts files into a .qrc files to include in the binary file
325	:type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
326	"""
327	if getattr(self, 'lang', None):
328		qmtasks = []
329		for x in self.to_list(self.lang):
330			if isinstance(x, str):
331				x = self.path.find_resource(x + '.ts')
332			qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.%d.qm' % self.idx)))
333
334		if getattr(self, 'update', None) and Options.options.trans_qt5:
335			cxxnodes = [a.inputs[0] for a in self.compiled_tasks] + [
336				a.inputs[0] for a in self.tasks if a.inputs and a.inputs[0].name.endswith('.ui')]
337			for x in qmtasks:
338				self.create_task('trans_update', cxxnodes, x.inputs)
339
340		if getattr(self, 'langname', None):
341			qmnodes = [x.outputs[0] for x in qmtasks]
342			rcnode = self.langname
343			if isinstance(rcnode, str):
344				rcnode = self.path.find_or_declare(rcnode + ('.%d.qrc' % self.idx))
345			t = self.create_task('qm2rcc', qmnodes, rcnode)
346			k = create_rcc_task(self, t.outputs[0])
347			self.link_task.inputs.append(k.outputs[0])
348
349	lst = []
350	for flag in self.to_list(self.env.CXXFLAGS):
351		if len(flag) < 2:
352			continue
353		f = flag[0:2]
354		if f in ('-D', '-I', '/D', '/I'):
355			if (f[0] == '/'):
356				lst.append('-' + flag[1:])
357			else:
358				lst.append(flag)
359	self.env.append_value('MOC_FLAGS', lst)
360
361@extension(*EXT_QT5)
362def cxx_hook(self, node):
363	"""
364	Re-maps C++ file extensions to the :py:class:`waflib.Tools.qt5.qxx` task.
365	"""
366	return self.create_compiled_task('qxx', node)
367
368class rcc(Task.Task):
369	"""
370	Processes ``.qrc`` files
371	"""
372	color   = 'BLUE'
373	run_str = '${QT_RCC} -name ${tsk.rcname()} ${SRC[0].abspath()} ${RCC_ST} -o ${TGT}'
374	ext_out = ['.h']
375
376	def rcname(self):
377		return os.path.splitext(self.inputs[0].name)[0]
378
379	def scan(self):
380		"""Parse the *.qrc* files"""
381		if not has_xml:
382			Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
383			return ([], [])
384
385		parser = make_parser()
386		curHandler = XMLHandler()
387		parser.setContentHandler(curHandler)
388		with open(self.inputs[0].abspath(), 'r') as f:
389			parser.parse(f)
390
391		nodes = []
392		names = []
393		root = self.inputs[0].parent
394		for x in curHandler.files:
395			nd = root.find_resource(x)
396			if nd:
397				nodes.append(nd)
398			else:
399				names.append(x)
400		return (nodes, names)
401
402	def quote_flag(self, x):
403		"""
404		Override Task.quote_flag. QT parses the argument files
405		differently than cl.exe and link.exe
406
407		:param x: flag
408		:type x: string
409		:return: quoted flag
410		:rtype: string
411		"""
412		return x
413
414
415class moc(Task.Task):
416	"""
417	Creates ``.moc`` files
418	"""
419	color   = 'BLUE'
420	run_str = '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
421
422	def quote_flag(self, x):
423		"""
424		Override Task.quote_flag. QT parses the argument files
425		differently than cl.exe and link.exe
426
427		:param x: flag
428		:type x: string
429		:return: quoted flag
430		:rtype: string
431		"""
432		return x
433
434
435class ui5(Task.Task):
436	"""
437	Processes ``.ui`` files
438	"""
439	color   = 'BLUE'
440	run_str = '${QT_UIC} ${SRC} -o ${TGT}'
441	ext_out = ['.h']
442
443class ts2qm(Task.Task):
444	"""
445	Generates ``.qm`` files from ``.ts`` files
446	"""
447	color   = 'BLUE'
448	run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
449
450class qm2rcc(Task.Task):
451	"""
452	Generates ``.qrc`` files from ``.qm`` files
453	"""
454	color = 'BLUE'
455	after = 'ts2qm'
456	def run(self):
457		"""Create a qrc file including the inputs"""
458		txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
459		code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
460		self.outputs[0].write(code)
461
462def configure(self):
463	"""
464	Besides the configuration options, the environment variable QT5_ROOT may be used
465	to give the location of the qt5 libraries (absolute path).
466
467	The detection uses the program ``pkg-config`` through :py:func:`waflib.Tools.config_c.check_cfg`
468	"""
469	self.find_qt5_binaries()
470	self.set_qt5_libs_dir()
471	self.set_qt5_libs_to_check()
472	self.set_qt5_defines()
473	self.find_qt5_libraries()
474	self.add_qt5_rpath()
475	self.simplify_qt5_libs()
476
477	# warn about this during the configuration too
478	if not has_xml:
479		Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
480
481	if 'COMPILER_CXX' not in self.env:
482		self.fatal('No CXX compiler defined: did you forget to configure compiler_cxx first?')
483
484	# Qt5 may be compiled with '-reduce-relocations' which requires dependent programs to have -fPIE or -fPIC?
485	frag = '#include <QApplication>\nint main(int argc, char **argv) {return 0;}\n'
486	uses = 'QT5CORE QT5WIDGETS QT5GUI'
487	for flag in [[], '-fPIE', '-fPIC', '-std=c++11' , ['-std=c++11', '-fPIE'], ['-std=c++11', '-fPIC']]:
488		msg = 'See if Qt files compile '
489		if flag:
490			msg += 'with %s' % flag
491		try:
492			self.check(features='qt5 cxx', use=uses, uselib_store='qt5', cxxflags=flag, fragment=frag, msg=msg)
493		except self.errors.ConfigurationError:
494			pass
495		else:
496			break
497	else:
498		self.fatal('Could not build a simple Qt application')
499
500	# FreeBSD does not add /usr/local/lib and the pkg-config files do not provide it either :-/
501	if Utils.unversioned_sys_platform() == 'freebsd':
502		frag = '#include <QApplication>\nint main(int argc, char **argv) { QApplication app(argc, argv); return NULL != (void*) (&app);}\n'
503		try:
504			self.check(features='qt5 cxx cxxprogram', use=uses, fragment=frag, msg='Can we link Qt programs on FreeBSD directly?')
505		except self.errors.ConfigurationError:
506			self.check(features='qt5 cxx cxxprogram', use=uses, uselib_store='qt5', libpath='/usr/local/lib', fragment=frag, msg='Is /usr/local/lib required?')
507
508@conf
509def find_qt5_binaries(self):
510	"""
511	Detects Qt programs such as qmake, moc, uic, lrelease
512	"""
513	env = self.env
514	opt = Options.options
515
516	qtdir = getattr(opt, 'qtdir', '')
517	qtbin = getattr(opt, 'qtbin', '')
518
519	paths = []
520
521	if qtdir:
522		qtbin = os.path.join(qtdir, 'bin')
523
524	# the qt directory has been given from QT5_ROOT - deduce the qt binary path
525	if not qtdir:
526		qtdir = self.environ.get('QT5_ROOT', '')
527		qtbin = self.environ.get('QT5_BIN') or os.path.join(qtdir, 'bin')
528
529	if qtbin:
530		paths = [qtbin]
531
532	# no qtdir, look in the path and in /usr/local/Trolltech
533	if not qtdir:
534		paths = self.environ.get('PATH', '').split(os.pathsep)
535		paths.extend(['/usr/share/qt5/bin', '/usr/local/lib/qt5/bin'])
536		try:
537			lst = Utils.listdir('/usr/local/Trolltech/')
538		except OSError:
539			pass
540		else:
541			if lst:
542				lst.sort()
543				lst.reverse()
544
545				# keep the highest version
546				qtdir = '/usr/local/Trolltech/%s/' % lst[0]
547				qtbin = os.path.join(qtdir, 'bin')
548				paths.append(qtbin)
549
550	# at the end, try to find qmake in the paths given
551	# keep the one with the highest version
552	cand = None
553	prev_ver = ['5', '0', '0']
554	for qmk in ('qmake-qt5', 'qmake5', 'qmake'):
555		try:
556			qmake = self.find_program(qmk, path_list=paths)
557		except self.errors.ConfigurationError:
558			pass
559		else:
560			try:
561				version = self.cmd_and_log(qmake + ['-query', 'QT_VERSION']).strip()
562			except self.errors.WafError:
563				pass
564			else:
565				if version:
566					new_ver = version.split('.')
567					if new_ver > prev_ver:
568						cand = qmake
569						prev_ver = new_ver
570
571	# qmake could not be found easily, rely on qtchooser
572	if not cand:
573		try:
574			self.find_program('qtchooser')
575		except self.errors.ConfigurationError:
576			pass
577		else:
578			cmd = self.env.QTCHOOSER + ['-qt=5', '-run-tool=qmake']
579			try:
580				version = self.cmd_and_log(cmd + ['-query', 'QT_VERSION'])
581			except self.errors.WafError:
582				pass
583			else:
584				cand = cmd
585
586	if cand:
587		self.env.QMAKE = cand
588	else:
589		self.fatal('Could not find qmake for qt5')
590
591	self.env.QT_HOST_BINS = qtbin = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_HOST_BINS']).strip()
592	paths.insert(0, qtbin)
593
594	def find_bin(lst, var):
595		if var in env:
596			return
597		for f in lst:
598			try:
599				ret = self.find_program(f, path_list=paths)
600			except self.errors.ConfigurationError:
601				pass
602			else:
603				env[var]=ret
604				break
605
606	find_bin(['uic-qt5', 'uic'], 'QT_UIC')
607	if not env.QT_UIC:
608		self.fatal('cannot find the uic compiler for qt5')
609
610	self.start_msg('Checking for uic version')
611	uicver = self.cmd_and_log(env.QT_UIC + ['-version'], output=Context.BOTH)
612	uicver = ''.join(uicver).strip()
613	uicver = uicver.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
614	self.end_msg(uicver)
615	if uicver.find(' 3.') != -1 or uicver.find(' 4.') != -1:
616		self.fatal('this uic compiler is for qt3 or qt4, add uic for qt5 to your path')
617
618	find_bin(['moc-qt5', 'moc'], 'QT_MOC')
619	find_bin(['rcc-qt5', 'rcc'], 'QT_RCC')
620	find_bin(['lrelease-qt5', 'lrelease'], 'QT_LRELEASE')
621	find_bin(['lupdate-qt5', 'lupdate'], 'QT_LUPDATE')
622
623	env.UIC_ST = '%s -o %s'
624	env.MOC_ST = '-o'
625	env.ui_PATTERN = 'ui_%s.h'
626	env.QT_LRELEASE_FLAGS = ['-silent']
627	env.MOCCPPPATH_ST = '-I%s'
628	env.MOCDEFINES_ST = '-D%s'
629
630@conf
631def set_qt5_libs_dir(self):
632	env = self.env
633	qtlibs = getattr(Options.options, 'qtlibs', None) or self.environ.get('QT5_LIBDIR')
634	if not qtlibs:
635		try:
636			qtlibs = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_LIBS']).strip()
637		except Errors.WafError:
638			qtdir = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_PREFIX']).strip()
639			qtlibs = os.path.join(qtdir, 'lib')
640	self.msg('Found the Qt5 libraries in', qtlibs)
641	env.QTLIBS = qtlibs
642
643@conf
644def find_single_qt5_lib(self, name, uselib, qtlibs, qtincludes, force_static):
645	env = self.env
646	if force_static:
647		exts = ('.a', '.lib')
648		prefix = 'STLIB'
649	else:
650		exts = ('.so', '.lib')
651		prefix = 'LIB'
652
653	def lib_names():
654		for x in exts:
655			for k in ('', '5') if Utils.is_win32 else ['']:
656				for p in ('lib', ''):
657					yield (p, name, k, x)
658
659	for tup in lib_names():
660		k = ''.join(tup)
661		path = os.path.join(qtlibs, k)
662		if os.path.exists(path):
663			if env.DEST_OS == 'win32':
664				libval = ''.join(tup[:-1])
665			else:
666				libval = name
667			env.append_unique(prefix + '_' + uselib, libval)
668			env.append_unique('%sPATH_%s' % (prefix, uselib), qtlibs)
669			env.append_unique('INCLUDES_' + uselib, qtincludes)
670			env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, name.replace('Qt5', 'Qt')))
671			return k
672	return False
673
674@conf
675def find_qt5_libraries(self):
676	env = self.env
677
678	qtincludes =  self.environ.get('QT5_INCLUDES') or self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_HEADERS']).strip()
679	force_static = self.environ.get('QT5_FORCE_STATIC')
680	try:
681		if self.environ.get('QT5_XCOMPILE'):
682			self.fatal('QT5_XCOMPILE Disables pkg-config detection')
683		self.check_cfg(atleast_pkgconfig_version='0.1')
684	except self.errors.ConfigurationError:
685		for i in self.qt5_vars:
686			uselib = i.upper()
687			if Utils.unversioned_sys_platform() == 'darwin':
688				# Since at least qt 4.7.3 each library locates in separate directory
689				fwk = i.replace('Qt5', 'Qt')
690				frameworkName = fwk + '.framework'
691
692				qtDynamicLib = os.path.join(env.QTLIBS, frameworkName, fwk)
693				if os.path.exists(qtDynamicLib):
694					env.append_unique('FRAMEWORK_' + uselib, fwk)
695					env.append_unique('FRAMEWORKPATH_' + uselib, env.QTLIBS)
696					self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
697				else:
698					self.msg('Checking for %s' % i, False, 'YELLOW')
699				env.append_unique('INCLUDES_' + uselib, os.path.join(env.QTLIBS, frameworkName, 'Headers'))
700			else:
701				ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, force_static)
702				if not force_static and not ret:
703					ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, True)
704				self.msg('Checking for %s' % i, ret, 'GREEN' if ret else 'YELLOW')
705	else:
706		path = '%s:%s:%s/pkgconfig:/usr/lib/qt5/lib/pkgconfig:/opt/qt5/lib/pkgconfig:/usr/lib/qt5/lib:/opt/qt5/lib' % (
707			self.environ.get('PKG_CONFIG_PATH', ''), env.QTLIBS, env.QTLIBS)
708		for i in self.qt5_vars:
709			self.check_cfg(package=i, args='--cflags --libs', mandatory=False, force_static=force_static, pkg_config_path=path)
710
711@conf
712def simplify_qt5_libs(self):
713	"""
714	Since library paths make really long command-lines,
715	and since everything depends on qtcore, remove the qtcore ones from qtgui, etc
716	"""
717	env = self.env
718	def process_lib(vars_, coreval):
719		for d in vars_:
720			var = d.upper()
721			if var == 'QTCORE':
722				continue
723
724			value = env['LIBPATH_'+var]
725			if value:
726				core = env[coreval]
727				accu = []
728				for lib in value:
729					if lib in core:
730						continue
731					accu.append(lib)
732				env['LIBPATH_'+var] = accu
733	process_lib(self.qt5_vars,       'LIBPATH_QTCORE')
734
735@conf
736def add_qt5_rpath(self):
737	"""
738	Defines rpath entries for Qt libraries
739	"""
740	env = self.env
741	if getattr(Options.options, 'want_rpath', False):
742		def process_rpath(vars_, coreval):
743			for d in vars_:
744				var = d.upper()
745				value = env['LIBPATH_' + var]
746				if value:
747					core = env[coreval]
748					accu = []
749					for lib in value:
750						if var != 'QTCORE':
751							if lib in core:
752								continue
753						accu.append('-Wl,--rpath='+lib)
754					env['RPATH_' + var] = accu
755		process_rpath(self.qt5_vars,       'LIBPATH_QTCORE')
756
757@conf
758def set_qt5_libs_to_check(self):
759	self.qt5_vars = Utils.to_list(getattr(self, 'qt5_vars', []))
760	if not self.qt5_vars:
761		dirlst = Utils.listdir(self.env.QTLIBS)
762
763		pat = self.env.cxxshlib_PATTERN
764		if Utils.is_win32:
765			pat = pat.replace('.dll', '.lib')
766		if self.environ.get('QT5_FORCE_STATIC'):
767			pat = self.env.cxxstlib_PATTERN
768		if Utils.unversioned_sys_platform() == 'darwin':
769			pat = r"%s\.framework"
770		re_qt = re.compile(pat%'Qt5?(?P<name>.*)'+'$')
771		for x in dirlst:
772			m = re_qt.match(x)
773			if m:
774				self.qt5_vars.append("Qt5%s" % m.group('name'))
775		if not self.qt5_vars:
776			self.fatal('cannot find any Qt5 library (%r)' % self.env.QTLIBS)
777
778	qtextralibs = getattr(Options.options, 'qtextralibs', None)
779	if qtextralibs:
780		self.qt5_vars.extend(qtextralibs.split(','))
781
782@conf
783def set_qt5_defines(self):
784	if sys.platform != 'win32':
785		return
786	for x in self.qt5_vars:
787		y=x.replace('Qt5', 'Qt')[2:].upper()
788		self.env.append_unique('DEFINES_%s' % x.upper(), 'QT_%s_LIB' % y)
789
790def options(opt):
791	"""
792	Command-line options
793	"""
794	opt.add_option('--want-rpath', action='store_true', default=False, dest='want_rpath', help='enable the rpath for qt libraries')
795	for i in 'qtdir qtbin qtlibs'.split():
796		opt.add_option('--'+i, type='string', default='', dest=i)
797
798	opt.add_option('--translate', action='store_true', help='collect translation strings', dest='trans_qt5', default=False)
799	opt.add_option('--qtextralibs', type='string', default='', dest='qtextralibs', help='additional qt libraries on the system to add to default ones, comma separated')
800
801