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 <QMap>\nint main(int argc, char **argv) {QMap<int,int> m;return m.keys().size();}\n' 486 uses = 'QT5CORE' 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 <QMap>\nint main(int argc, char **argv) {QMap<int,int> m;return m.keys().size();}\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