Varun Patil | 3d85090 | 2020-11-23 12:19:14 +0530 | [diff] [blame] | 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. The list of |
| 61 | libraries to be requested to pkg-config is formulated by scanning |
| 62 | in the QTLIBS directory (that can be passed via --qtlibs or by |
| 63 | setting the environment variable QT5_LIBDIR otherwise is derived |
| 64 | by querying qmake for QT_INSTALL_LIBS directory) for shared/static |
| 65 | libraries present. |
| 66 | Alternatively the list of libraries to be requested via pkg-config |
| 67 | can be set using the qt5_vars attribute, ie: |
| 68 | |
| 69 | conf.qt5_vars = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Test']; |
| 70 | |
| 71 | This can speed up configuration phase if needed libraries are |
| 72 | known beforehand, can improve detection on systems with a |
| 73 | sparse QT5 libraries installation (ie. NIX) and can improve |
| 74 | detection of some header-only Qt modules (ie. Qt5UiPlugin). |
| 75 | |
| 76 | To force static library detection use: |
| 77 | QT5_XCOMPILE=1 QT5_FORCE_STATIC=1 waf configure |
| 78 | """ |
| 79 | |
| 80 | from __future__ import with_statement |
| 81 | |
| 82 | try: |
| 83 | from xml.sax import make_parser |
| 84 | from xml.sax.handler import ContentHandler |
| 85 | except ImportError: |
| 86 | has_xml = False |
| 87 | ContentHandler = object |
| 88 | else: |
| 89 | has_xml = True |
| 90 | |
| 91 | import os, sys, re |
| 92 | from waflib.Tools import cxx |
| 93 | from waflib import Build, Task, Utils, Options, Errors, Context |
| 94 | from waflib.TaskGen import feature, after_method, extension, before_method |
| 95 | from waflib.Configure import conf |
| 96 | from waflib import Logs |
| 97 | |
| 98 | MOC_H = ['.h', '.hpp', '.hxx', '.hh'] |
| 99 | """ |
| 100 | File extensions associated to .moc files |
| 101 | """ |
| 102 | |
| 103 | EXT_RCC = ['.qrc'] |
| 104 | """ |
| 105 | File extension for the resource (.qrc) files |
| 106 | """ |
| 107 | |
| 108 | EXT_UI = ['.ui'] |
| 109 | """ |
| 110 | File extension for the user interface (.ui) files |
| 111 | """ |
| 112 | |
| 113 | EXT_QT5 = ['.cpp', '.cc', '.cxx', '.C'] |
| 114 | """ |
| 115 | File extensions of C++ files that may require a .moc processing |
| 116 | """ |
| 117 | |
| 118 | class 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 and False: |
| 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 | |
| 245 | class 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 | |
| 250 | class 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) |
| 268 | def 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) |
| 280 | def 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') |
| 299 | def 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') |
| 305 | def 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') |
| 327 | def 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) |
| 378 | def 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 | |
| 384 | class 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 | |
| 431 | class 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 | |
| 451 | class 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 | |
| 459 | class 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 | |
| 466 | class 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 | |
| 478 | def 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' |
Varun Patil | a24bd3e | 2020-11-24 10:08:33 +0530 | [diff] [blame] | 503 | for flag in [[], '-fPIE', '-fPIC', '-std=c++14' , ['-std=c++14', '-fPIE'], ['-std=c++14', '-fPIC']]: |
Varun Patil | 3d85090 | 2020-11-23 12:19:14 +0530 | [diff] [blame] | 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 |
| 525 | def 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 |
| 647 | def 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 |
| 660 | def 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 |
| 691 | def 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 |
| 728 | def 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 |
| 752 | def 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 |
| 774 | def 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 |
| 799 | def 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 | |
| 806 | def 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 | |