84d121a844f16439ed8c691c01e851efe371323c
[sfrench/samba-autobuild/.git] / buildtools / wafadmin / Tools / qt4.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2006 (ita)
4
5 """
6 Qt4 support
7
8 If QT4_ROOT is given (absolute path), the configuration will look in it first
9
10 This module also demonstrates how to add tasks dynamically (when the build has started)
11 """
12
13 try:
14         from xml.sax import make_parser
15         from xml.sax.handler import ContentHandler
16 except ImportError:
17         has_xml = False
18         ContentHandler = object
19 else:
20         has_xml = True
21
22 import os, sys
23 import ccroot, cxx
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 *
28
29 MOC_H = ['.h', '.hpp', '.hxx', '.hh']
30 EXT_RCC = ['.qrc']
31 EXT_UI  = ['.ui']
32 EXT_QT4 = ['.cpp', '.cc', '.cxx', '.C']
33
34 class qxx_task(Task.Task):
35         "A cpp task that may create a moc task dynamically"
36
37         before = ['cxx_link', 'static_link']
38
39         def __init__(self, *k, **kw):
40                 Task.Task.__init__(self, *k, **kw)
41                 self.moc_done = 0
42
43         def scan(self):
44                 (nodes, names) = ccroot.scan(self)
45                 # for some reasons (variants) the moc node may end in the list of node deps
46                 for x in nodes:
47                         if x.name.endswith('.moc'):
48                                 nodes.remove(x)
49                                 names.append(x.relpath_gen(self.inputs[0].parent))
50                 return (nodes, names)
51
52         def runnable_status(self):
53                 if self.moc_done:
54                         # if there is a moc task, delay the computation of the file signature
55                         for t in self.run_after:
56                                 if not t.hasrun:
57                                         return ASK_LATER
58                         # the moc file enters in the dependency calculation
59                         # so we need to recompute the signature when the moc file is present
60                         self.signature()
61                         return Task.Task.runnable_status(self)
62                 else:
63                         # yes, really, there are people who generate cxx files
64                         for t in self.run_after:
65                                 if not t.hasrun:
66                                         return ASK_LATER
67                         self.add_moc_tasks()
68                         return ASK_LATER
69
70         def add_moc_tasks(self):
71
72                 node = self.inputs[0]
73                 tree = node.__class__.bld
74
75                 try:
76                         # compute the signature once to know if there is a moc file to create
77                         self.signature()
78                 except KeyError:
79                         # the moc file may be referenced somewhere else
80                         pass
81                 else:
82                         # remove the signature, it must be recomputed with the moc task
83                         delattr(self, 'cache_sig')
84
85                 moctasks=[]
86                 mocfiles=[]
87                 variant = node.variant(self.env)
88                 try:
89                         tmp_lst = tree.raw_deps[self.unique_id()]
90                         tree.raw_deps[self.unique_id()] = []
91                 except KeyError:
92                         tmp_lst = []
93                 for d in tmp_lst:
94                         if not d.endswith('.moc'): continue
95                         # paranoid check
96                         if d in mocfiles:
97                                 error("paranoia owns")
98                                 continue
99
100                         # process that base.moc only once
101                         mocfiles.append(d)
102
103                         # find the extension (performed only when the .cpp has changes)
104                         base2 = d[:-4]
105                         for path in [node.parent] + self.generator.env['INC_PATHS']:
106                                 tree.rescan(path)
107                                 vals = getattr(Options.options, 'qt_header_ext', '') or MOC_H
108                                 for ex in vals:
109                                         h_node = path.find_resource(base2 + ex)
110                                         if h_node:
111                                                 break
112                                 else:
113                                         continue
114                                 break
115                         else:
116                                 raise Utils.WafError("no header found for %s which is a moc file" % str(d))
117
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
120
121                         # create the task
122                         task = Task.TaskBase.classes['moc'](self.env, normal=0)
123                         task.set_inputs(h_node)
124                         task.set_outputs(m_node)
125
126                         generator = tree.generator
127                         generator.outstanding.insert(0, task)
128                         generator.total += 1
129
130                         moctasks.append(task)
131
132                 # remove raw deps except the moc files to save space (optimization)
133                 tmp_lst = tree.raw_deps[self.unique_id()] = mocfiles
134
135                 # look at the file inputs, it is set right above
136                 lst = tree.node_deps.get(self.unique_id(), ())
137                 for d in lst:
138                         name = d.name
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
142                                 task.set_outputs(d)
143
144                                 generator = tree.generator
145                                 generator.outstanding.insert(0, task)
146                                 generator.total += 1
147
148                                 moctasks.append(task)
149
150                 # simple scheduler dependency: run the moc task before others
151                 self.run_after = moctasks
152                 self.moc_done = 1
153
154         run = Task.TaskBase.classes['cxx'].__dict__['run']
155
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']
160
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)
166
167 class XMLHandler(ContentHandler):
168         def __init__(self):
169                 self.buf = []
170                 self.files = []
171         def startElement(self, name, attrs):
172                 if name == 'file':
173                         self.buf = []
174         def endElement(self, name):
175                 if name == 'file':
176                         self.files.append(''.join(self.buf))
177         def characters(self, cars):
178                 self.buf.append(cars)
179
180 def scan(self):
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))
187         parser.parse(fi)
188         fi.close()
189
190         nodes = []
191         names = []
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)
197
198         return (nodes, names)
199
200 @extension(EXT_RCC)
201 def create_rcc_task(self, node):
202         "hook for rcc files"
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)
207         return cpptask
208
209 @extension(EXT_UI)
210 def create_uic_task(self, node):
211         "hook for uic tasks"
212         uictask = self.create_task('ui4', node)
213         uictask.outputs = [self.path.find_or_declare(self.env['ui_PATTERN'] % node.name[:-3])]
214         return uictask
215
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')
220
221 @extension('.ts')
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]
225
226 @feature('qt4')
227 @after('apply_link')
228 def apply_qt4(self):
229         if getattr(self, 'lang', None):
230                 update = getattr(self, 'update', None)
231                 lst=[]
232                 trans=[]
233                 for l in self.to_list(self.lang):
234
235                         if not isinstance(l, Node.Node):
236                                 l = self.path.find_resource(l+'.ts')
237
238                         t = self.create_task('ts2qm', l, l.change_ext('.qm'))
239                         lst.append(t.outputs[0])
240
241                         if update:
242                                 trans.append(t.inputs[0])
243
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]
250                         u.outputs = trans
251
252                 if getattr(self, 'langname', None):
253                         t = Task.TaskBase.classes['qm2rcc'](self.env)
254                         t.set_inputs(lst)
255                         t.set_outputs(self.path.find_or_declare(self.langname+'.qrc'))
256                         t.path = self.path
257                         k = create_rcc_task(self, t.outputs[0])
258                         self.link_task.inputs.append(k.outputs[0])
259
260         self.env.append_value('MOC_FLAGS', self.env._CXXDEFFLAGS)
261         self.env.append_value('MOC_FLAGS', self.env._CXXINCFLAGS)
262
263 @extension(EXT_QT4)
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
268
269         task = self.create_task('qxx', node, node.change_ext(obj_ext))
270         self.compiled_tasks.append(task)
271         return task
272
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:
278                 f.write(' <file>')
279                 #f.write(k.name)
280                 f.write(k.path_to_parent(task.path))
281                 f.write('</file>\n')
282         f.write('</qresource>\n</RCC>')
283         f.close()
284
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)
288 cls.scan = scan
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)
291
292 Task.task_type_from_func('qm2rcc', vars=[], func=process_qm2rcc, color='BLUE', before='rcc', after='ts2qm')
293
294 def detect_qt4(conf):
295         env = conf.env
296         opt = Options.options
297
298         qtdir = getattr(opt, 'qtdir', '')
299         qtbin = getattr(opt, 'qtbin', '')
300         qtlibs = getattr(opt, 'qtlibs', '')
301         useframework = getattr(opt, 'use_qt4_osxframework', True)
302
303         paths = []
304
305         # the path to qmake has been given explicitely
306         if qtbin:
307                 paths = [qtbin]
308
309         # the qt directory has been given - we deduce the qt binary path
310         if not qtdir:
311                 qtdir = conf.environ.get('QT4_ROOT', '')
312                 qtbin = os.path.join(qtdir, 'bin')
313                 paths = [qtbin]
314
315         # no qtdir, look in the path and in /usr/local/Trolltech
316         if not qtdir:
317                 paths = os.environ.get('PATH', '').split(os.pathsep)
318                 paths.append('/usr/share/qt4/bin/')
319                 try:
320                         lst = os.listdir('/usr/local/Trolltech/')
321                 except OSError:
322                         pass
323                 else:
324                         if lst:
325                                 lst.sort()
326                                 lst.reverse()
327
328                                 # keep the highest version
329                                 qtdir = '/usr/local/Trolltech/%s/' % lst[0]
330                                 qtbin = os.path.join(qtdir, 'bin')
331                                 paths.append(qtbin)
332
333         # at the end, try to find qmake in the paths given
334         # keep the one with the highest version
335         cand = None
336         prev_ver = ['4', '0', '0']
337         for qmk in ['qmake-qt4', 'qmake4', 'qmake']:
338                 qmake = conf.find_program(qmk, path_list=paths)
339                 if qmake:
340                         try:
341                                 version = Utils.cmd_output([qmake, '-query', 'QT_VERSION']).strip()
342                         except ValueError:
343                                 pass
344                         else:
345                                 if version:
346                                         new_ver = version.split('.')
347                                         if new_ver > prev_ver:
348                                                 cand = qmake
349                                                 prev_ver = new_ver
350         if cand:
351                 qmake = cand
352         else:
353                 conf.fatal('could not find qmake for qt4')
354
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
359
360         if not qtlibs:
361                 try:
362                         qtlibs = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_LIBS']).strip() + os.sep
363                 except ValueError:
364                         qtlibs = os.path.join(qtdir, 'lib')
365
366         def find_bin(lst, var):
367                 for f in lst:
368                         ret = conf.find_program(f, path_list=paths)
369                         if ret:
370                                 env[var]=ret
371                                 break
372
373         vars = "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtWebKit Qt3Support".split()
374
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')
379
380         try:
381                 version = Utils.cmd_output(env['QT_UIC'] + " -version 2>&1").strip()
382         except ValueError:
383                 conf.fatal('your uic compiler is for qt3, add uic for qt4 to your path')
384
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)
389                 sys.exit(1)
390         conf.check_message('uic version', '', 1, option='(%s)'%version)
391
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')
396
397         env['UIC3_ST']= '%s -o %s'
398         env['UIC_ST'] = '%s -o %s'
399         env['MOC_ST'] = '-o'
400         env['ui_PATTERN'] = 'ui_%s.h'
401         env['QT_LRELEASE_FLAGS'] = ['-silent']
402
403         vars_debug = [a+'_debug' for a in vars]
404
405         try:
406                 conf.find_program('pkg-config', var='pkgconfig', path_list=paths, mandatory=True)
407
408         except Configure.ConfigurationError:
409
410                 for lib in vars_debug+vars:
411                         uselib = lib.upper()
412
413                         d = (lib.find('_debug') > 0) and 'd' or ''
414
415                         # original author seems to prefer static to shared libraries
416                         for (pat, kind) in ((conf.env.staticlib_PATTERN, 'STATIC'), (conf.env.shlib_PATTERN, '')):
417
418                                 conf.check_message_1('Checking for %s %s' % (lib, kind))
419
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')
425                                                 break
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')
430                                                 break
431                                 else:
432                                         conf.check_message_2('not found', 'YELLOW')
433                                         continue
434                                 break
435
436                         env.append_unique('LIBPATH_' + uselib, qtlibs)
437                         env.append_unique('CPPPATH_' + uselib, qtincludes)
438                         env.append_unique('CPPPATH_' + uselib, qtincludes + os.sep + lib)
439         else:
440                 for i in vars_debug+vars:
441                         try:
442                                 conf.check_cfg(package=i, args='--cflags --libs --silence-errors', path=conf.env.pkgconfig)
443                         except ValueError:
444                                 pass
445
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):
449                 for d in vars_:
450                         var = d.upper()
451                         if var == 'QTCORE': continue
452
453                         value = env['LIBPATH_'+var]
454                         if value:
455                                 core = env[coreval]
456                                 accu = []
457                                 for lib in value:
458                                         if lib in core: continue
459                                         accu.append(lib)
460                                 env['LIBPATH_'+var] = accu
461
462         process_lib(vars, 'LIBPATH_QTCORE')
463         process_lib(vars_debug, 'LIBPATH_QTCORE_DEBUG')
464
465         # rpath if wanted
466         want_rpath = getattr(Options.options, 'want_rpath', 1)
467         if want_rpath:
468                 def process_rpath(vars_, coreval):
469                         for d in vars_:
470                                 var = d.upper()
471                                 value = env['LIBPATH_'+var]
472                                 if value:
473                                         core = env[coreval]
474                                         accu = []
475                                         for lib in value:
476                                                 if var != 'QTCORE':
477                                                         if lib in core:
478                                                                 continue
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')
483
484         env['QTLOCALE'] = str(env['PREFIX'])+'/share/locale'
485
486 def detect(conf):
487         detect_qt4(conf)
488
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]')
491
492         opt.add_option('--header-ext',
493                 type='string',
494                 default='',
495                 help='header extension for moc files',
496                 dest='qt_header_ext')
497
498         for i in 'qtdir qtbin qtlibs'.split():
499                 opt.add_option('--'+i, type='string', default='', dest=i)
500
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)
503
504         opt.add_option('--translate', action="store_true", help="collect translation strings", dest="trans_qt4", default=False)
505