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