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