3 # Thomas Nagy, 2006 (ita)
8 If QT4_ROOT is given (absolute path), the configuration will look in it first
10 This module also demonstrates how to add tasks dynamically (when the build has started)
14 from xml
.sax
import make_parser
15 from xml
.sax
.handler
import ContentHandler
18 ContentHandler
= object
24 import TaskGen
, Task
, Utils
, Runner
, Options
, Node
, Configure
25 from TaskGen
import taskgen
, feature
, after
, extension
26 from Logs
import error
27 from Constants
import *
29 MOC_H
= ['.h', '.hpp', '.hxx', '.hh']
32 EXT_QT4
= ['.cpp', '.cc', '.cxx', '.C']
34 class qxx_task(Task
.Task
):
35 "A cpp task that may create a moc task dynamically"
37 before
= ['cxx_link', 'static_link']
39 def __init__(self
, *k
, **kw
):
40 Task
.Task
.__init
__(self
, *k
, **kw
)
44 (nodes
, names
) = ccroot
.scan(self
)
45 # for some reasons (variants) the moc node may end in the list of node deps
47 if x
.name
.endswith('.moc'):
49 names
.append(x
.relpath_gen(self
.inputs
[0].parent
))
52 def runnable_status(self
):
54 # if there is a moc task, delay the computation of the file signature
55 for t
in self
.run_after
:
58 # the moc file enters in the dependency calculation
59 # so we need to recompute the signature when the moc file is present
61 return Task
.Task
.runnable_status(self
)
63 # yes, really, there are people who generate cxx files
64 for t
in self
.run_after
:
70 def add_moc_tasks(self
):
73 tree
= node
.__class
__.bld
76 # compute the signature once to know if there is a moc file to create
79 # the moc file may be referenced somewhere else
82 # remove the signature, it must be recomputed with the moc task
83 delattr(self
, 'cache_sig')
87 variant
= node
.variant(self
.env
)
89 tmp_lst
= tree
.raw_deps
[self
.unique_id()]
90 tree
.raw_deps
[self
.unique_id()] = []
94 if not d
.endswith('.moc'): continue
97 error("paranoia owns")
100 # process that base.moc only once
103 # find the extension (performed only when the .cpp has changes)
105 for path
in [node
.parent
] + self
.generator
.env
['INC_PATHS']:
107 vals
= getattr(Options
.options
, 'qt_header_ext', '') or MOC_H
109 h_node
= path
.find_resource(base2
+ ex
)
116 raise Utils
.WafError("no header found for %s which is a moc file" % str(d
))
118 m_node
= h_node
.change_ext('.moc')
119 tree
.node_deps
[(self
.inputs
[0].parent
.id, self
.env
.variant(), m_node
.name
)] = h_node
122 task
= Task
.TaskBase
.classes
['moc'](self
.env
, normal
=0)
123 task
.set_inputs(h_node
)
124 task
.set_outputs(m_node
)
126 generator
= tree
.generator
127 generator
.outstanding
.insert(0, task
)
130 moctasks
.append(task
)
132 # remove raw deps except the moc files to save space (optimization)
133 tmp_lst
= tree
.raw_deps
[self
.unique_id()] = mocfiles
135 # look at the file inputs, it is set right above
136 lst
= tree
.node_deps
.get(self
.unique_id(), ())
139 if name
.endswith('.moc'):
140 task
= Task
.TaskBase
.classes
['moc'](self
.env
, normal
=0)
141 task
.set_inputs(tree
.node_deps
[(self
.inputs
[0].parent
.id, self
.env
.variant(), name
)]) # 1st element in a tuple
144 generator
= tree
.generator
145 generator
.outstanding
.insert(0, task
)
148 moctasks
.append(task
)
150 # simple scheduler dependency: run the moc task before others
151 self
.run_after
= moctasks
154 run
= Task
.TaskBase
.classes
['cxx'].__dict
__['run']
156 def translation_update(task
):
157 outs
= [a
.abspath(task
.env
) for a
in task
.outputs
]
158 outs
= " ".join(outs
)
159 lupdate
= task
.env
['QT_LUPDATE']
161 for x
in task
.inputs
:
162 file = x
.abspath(task
.env
)
163 cmd
= "%s %s -ts %s" % (lupdate
, file, outs
)
164 Utils
.pprint('BLUE', cmd
)
165 task
.generator
.bld
.exec_command(cmd
)
167 class XMLHandler(ContentHandler
):
171 def startElement(self
, name
, attrs
):
174 def endElement(self
, name
):
176 self
.files
.append(''.join(self
.buf
))
177 def characters(self
, cars
):
178 self
.buf
.append(cars
)
181 "add the dependency on the files referenced in the qrc"
182 node
= self
.inputs
[0]
183 parser
= make_parser()
184 curHandler
= XMLHandler()
185 parser
.setContentHandler(curHandler
)
186 fi
= open(self
.inputs
[0].abspath(self
.env
))
192 root
= self
.inputs
[0].parent
193 for x
in curHandler
.files
:
194 nd
= root
.find_resource(x
)
195 if nd
: nodes
.append(nd
)
196 else: names
.append(x
)
198 return (nodes
, names
)
201 def create_rcc_task(self
, node
):
203 rcnode
= node
.change_ext('_rc.cpp')
204 rcctask
= self
.create_task('rcc', node
, rcnode
)
205 cpptask
= self
.create_task('cxx', rcnode
, rcnode
.change_ext('.o'))
206 self
.compiled_tasks
.append(cpptask
)
210 def create_uic_task(self
, node
):
212 uictask
= self
.create_task('ui4', node
)
213 uictask
.outputs
= [self
.path
.find_or_declare(self
.env
['ui_PATTERN'] % node
.name
[:-3])]
216 class qt4_taskgen(cxx
.cxx_taskgen
):
217 def __init__(self
, *k
, **kw
):
218 cxx
.cxx_taskgen
.__init
__(self
, *k
, **kw
)
219 self
.features
.append('qt4')
222 def add_lang(self
, node
):
223 """add all the .ts file into self.lang"""
224 self
.lang
= self
.to_list(getattr(self
, 'lang', [])) + [node
]
229 if getattr(self
, 'lang', None):
230 update
= getattr(self
, 'update', None)
233 for l
in self
.to_list(self
.lang
):
235 if not isinstance(l
, Node
.Node
):
236 l
= self
.path
.find_resource(l
+'.ts')
238 t
= self
.create_task('ts2qm', l
, l
.change_ext('.qm'))
239 lst
.append(t
.outputs
[0])
242 trans
.append(t
.inputs
[0])
244 trans_qt4
= getattr(Options
.options
, 'trans_qt4', False)
245 if update
and trans_qt4
:
246 # we need the cpp files given, except the rcc task we create after
247 # FIXME may be broken
248 u
= Task
.TaskCmd(translation_update
, self
.env
, 2)
249 u
.inputs
= [a
.inputs
[0] for a
in self
.compiled_tasks
]
252 if getattr(self
, 'langname', None):
253 t
= Task
.TaskBase
.classes
['qm2rcc'](self
.env
)
255 t
.set_outputs(self
.path
.find_or_declare(self
.langname
+'.qrc'))
257 k
= create_rcc_task(self
, t
.outputs
[0])
258 self
.link_task
.inputs
.append(k
.outputs
[0])
260 self
.env
.append_value('MOC_FLAGS', self
.env
._CXXDEFFLAGS
)
261 self
.env
.append_value('MOC_FLAGS', self
.env
._CXXINCFLAGS
)
264 def cxx_hook(self
, node
):
265 # create the compilation task: cpp or cc
266 try: obj_ext
= self
.obj_ext
267 except AttributeError: obj_ext
= '_%d.o' % self
.idx
269 task
= self
.create_task('qxx', node
, node
.change_ext(obj_ext
))
270 self
.compiled_tasks
.append(task
)
273 def process_qm2rcc(task
):
274 outfile
= task
.outputs
[0].abspath(task
.env
)
275 f
= open(outfile
, 'w')
276 f
.write('<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n')
277 for k
in task
.inputs
:
280 f
.write(k
.path_to_parent(task
.path
))
282 f
.write('</qresource>\n</RCC>')
285 b
= Task
.simple_task_type
286 b('moc', '${QT_MOC} ${MOC_FLAGS} ${SRC} ${MOC_ST} ${TGT}', color
='BLUE', vars=['QT_MOC', 'MOC_FLAGS'], shell
=False)
287 cls
= b('rcc', '${QT_RCC} -name ${SRC[0].name} ${SRC[0].abspath(env)} ${RCC_ST} -o ${TGT}', color
='BLUE', before
='cxx moc qxx_task', after
="qm2rcc", shell
=False)
289 b('ui4', '${QT_UIC} ${SRC} -o ${TGT}', color
='BLUE', before
='cxx moc qxx_task', shell
=False)
290 b('ts2qm', '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}', color
='BLUE', before
='qm2rcc', shell
=False)
292 Task
.task_type_from_func('qm2rcc', vars=[], func
=process_qm2rcc
, color
='BLUE', before
='rcc', after
='ts2qm')
294 def detect_qt4(conf
):
296 opt
= Options
.options
298 qtdir
= getattr(opt
, 'qtdir', '')
299 qtbin
= getattr(opt
, 'qtbin', '')
300 qtlibs
= getattr(opt
, 'qtlibs', '')
301 useframework
= getattr(opt
, 'use_qt4_osxframework', True)
305 # the path to qmake has been given explicitely
309 # the qt directory has been given - we deduce the qt binary path
311 qtdir
= conf
.environ
.get('QT4_ROOT', '')
312 qtbin
= os
.path
.join(qtdir
, 'bin')
315 # no qtdir, look in the path and in /usr/local/Trolltech
317 paths
= os
.environ
.get('PATH', '').split(os
.pathsep
)
318 paths
.append('/usr/share/qt4/bin/')
320 lst
= os
.listdir('/usr/local/Trolltech/')
328 # keep the highest version
329 qtdir
= '/usr/local/Trolltech/%s/' % lst
[0]
330 qtbin
= os
.path
.join(qtdir
, 'bin')
333 # at the end, try to find qmake in the paths given
334 # keep the one with the highest version
336 prev_ver
= ['4', '0', '0']
337 for qmk
in ['qmake-qt4', 'qmake4', 'qmake']:
338 qmake
= conf
.find_program(qmk
, path_list
=paths
)
341 version
= Utils
.cmd_output([qmake
, '-query', 'QT_VERSION']).strip()
346 new_ver
= version
.split('.')
347 if new_ver
> prev_ver
:
353 conf
.fatal('could not find qmake for qt4')
355 conf
.env
.QMAKE
= qmake
356 qtincludes
= Utils
.cmd_output([qmake
, '-query', 'QT_INSTALL_HEADERS']).strip()
357 qtdir
= Utils
.cmd_output([qmake
, '-query', 'QT_INSTALL_PREFIX']).strip() + os
.sep
358 qtbin
= Utils
.cmd_output([qmake
, '-query', 'QT_INSTALL_BINS']).strip() + os
.sep
362 qtlibs
= Utils
.cmd_output([qmake
, '-query', 'QT_INSTALL_LIBS']).strip() + os
.sep
364 qtlibs
= os
.path
.join(qtdir
, 'lib')
366 def find_bin(lst
, var
):
368 ret
= conf
.find_program(f
, path_list
=paths
)
373 vars = "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtWebKit Qt3Support".split()
375 find_bin(['uic-qt3', 'uic3'], 'QT_UIC3')
376 find_bin(['uic-qt4', 'uic'], 'QT_UIC')
377 if not env
['QT_UIC']:
378 conf
.fatal('cannot find the uic compiler for qt4')
381 version
= Utils
.cmd_output(env
['QT_UIC'] + " -version 2>&1").strip()
383 conf
.fatal('your uic compiler is for qt3, add uic for qt4 to your path')
385 version
= version
.replace('Qt User Interface Compiler ','')
386 version
= version
.replace('User Interface Compiler for Qt', '')
387 if version
.find(" 3.") != -1:
388 conf
.check_message('uic version', '(too old)', 0, option
='(%s)'%version
)
390 conf
.check_message('uic version', '', 1, option
='(%s)'%version
)
392 find_bin(['moc-qt4', 'moc'], 'QT_MOC')
393 find_bin(['rcc'], 'QT_RCC')
394 find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE')
395 find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE')
397 env
['UIC3_ST']= '%s -o %s'
398 env
['UIC_ST'] = '%s -o %s'
400 env
['ui_PATTERN'] = 'ui_%s.h'
401 env
['QT_LRELEASE_FLAGS'] = ['-silent']
403 vars_debug
= [a
+'_debug' for a
in vars]
406 conf
.find_program('pkg-config', var
='pkgconfig', path_list
=paths
, mandatory
=True)
408 except Configure
.ConfigurationError
:
410 for lib
in vars_debug
+vars:
413 d
= (lib
.find('_debug') > 0) and 'd' or ''
415 # original author seems to prefer static to shared libraries
416 for (pat
, kind
) in ((conf
.env
.staticlib_PATTERN
, 'STATIC'), (conf
.env
.shlib_PATTERN
, '')):
418 conf
.check_message_1('Checking for %s %s' % (lib
, kind
))
420 for ext
in ['', '4']:
421 path
= os
.path
.join(qtlibs
, pat
% (lib
+ d
+ ext
))
422 if os
.path
.exists(path
):
423 env
.append_unique(kind
+ 'LIB_' + uselib
, lib
+ d
+ ext
)
424 conf
.check_message_2('ok ' + path
, 'GREEN')
426 path
= os
.path
.join(qtbin
, pat
% (lib
+ d
+ ext
))
427 if os
.path
.exists(path
):
428 env
.append_unique(kind
+ 'LIB_' + uselib
, lib
+ d
+ ext
)
429 conf
.check_message_2('ok ' + path
, 'GREEN')
432 conf
.check_message_2('not found', 'YELLOW')
436 env
.append_unique('LIBPATH_' + uselib
, qtlibs
)
437 env
.append_unique('CPPPATH_' + uselib
, qtincludes
)
438 env
.append_unique('CPPPATH_' + uselib
, qtincludes
+ os
.sep
+ lib
)
440 for i
in vars_debug
+vars:
442 conf
.check_cfg(package
=i
, args
='--cflags --libs --silence-errors', path
=conf
.env
.pkgconfig
)
446 # the libpaths are set nicely, unfortunately they make really long command-lines
447 # remove the qtcore ones from qtgui, etc
448 def process_lib(vars_
, coreval
):
451 if var
== 'QTCORE': continue
453 value
= env
['LIBPATH_'+var
]
458 if lib
in core
: continue
460 env
['LIBPATH_'+var
] = accu
462 process_lib(vars, 'LIBPATH_QTCORE')
463 process_lib(vars_debug
, 'LIBPATH_QTCORE_DEBUG')
466 want_rpath
= getattr(Options
.options
, 'want_rpath', 1)
468 def process_rpath(vars_
, coreval
):
471 value
= env
['LIBPATH_'+var
]
479 accu
.append('-Wl,--rpath='+lib
)
480 env
['RPATH_'+var
] = accu
481 process_rpath(vars, 'LIBPATH_QTCORE')
482 process_rpath(vars_debug
, 'LIBPATH_QTCORE_DEBUG')
484 env
['QTLOCALE'] = str(env
['PREFIX'])+'/share/locale'
489 def set_options(opt
):
490 opt
.add_option('--want-rpath', type='int', default
=1, dest
='want_rpath', help='set rpath to 1 or 0 [Default 1]')
492 opt
.add_option('--header-ext',
495 help='header extension for moc files',
496 dest
='qt_header_ext')
498 for i
in 'qtdir qtbin qtlibs'.split():
499 opt
.add_option('--'+i
, type='string', default
='', dest
=i
)
501 if sys
.platform
== "darwin":
502 opt
.add_option('--no-qt4-framework', action
="store_false", help='do not use the framework version of Qt4 in OS X', dest
='use_qt4_osxframework',default
=True)
504 opt
.add_option('--translate', action
="store_true", help="collect translation strings", dest
="trans_qt4", default
=False)