#!/usr/bin/env python # encoding: utf-8 # Thomas Nagy, 2006 (ita) """ Qt4 support If QT4_ROOT is given (absolute path), the configuration will look in it first This module also demonstrates how to add tasks dynamically (when the build has started) """ try: from xml.sax import make_parser from xml.sax.handler import ContentHandler except ImportError: has_xml = False ContentHandler = object else: has_xml = True import os, sys import ccroot, cxx import TaskGen, Task, Utils, Runner, Options, Node, Configure from TaskGen import taskgen, feature, after, extension from Logs import error from Constants import * MOC_H = ['.h', '.hpp', '.hxx', '.hh'] EXT_RCC = ['.qrc'] EXT_UI = ['.ui'] EXT_QT4 = ['.cpp', '.cc', '.cxx', '.C'] class qxx_task(Task.Task): "A cpp task that may create a moc task dynamically" before = ['cxx_link', 'static_link'] def __init__(self, *k, **kw): Task.Task.__init__(self, *k, **kw) self.moc_done = 0 def scan(self): (nodes, names) = ccroot.scan(self) # for some reasons (variants) the moc node may end in the list of node deps for x in nodes: if x.name.endswith('.moc'): nodes.remove(x) names.append(x.relpath_gen(self.inputs[0].parent)) return (nodes, names) def runnable_status(self): if self.moc_done: # if there is a moc task, delay the computation of the file signature for t in self.run_after: if not t.hasrun: return ASK_LATER # the moc file enters in the dependency calculation # so we need to recompute the signature when the moc file is present self.signature() return Task.Task.runnable_status(self) else: # yes, really, there are people who generate cxx files for t in self.run_after: if not t.hasrun: return ASK_LATER self.add_moc_tasks() return ASK_LATER def add_moc_tasks(self): node = self.inputs[0] tree = node.__class__.bld try: # compute the signature once to know if there is a moc file to create self.signature() except KeyError: # the moc file may be referenced somewhere else pass else: # remove the signature, it must be recomputed with the moc task delattr(self, 'cache_sig') moctasks=[] mocfiles=[] variant = node.variant(self.env) try: tmp_lst = tree.raw_deps[self.unique_id()] tree.raw_deps[self.unique_id()] = [] except KeyError: tmp_lst = [] for d in tmp_lst: if not d.endswith('.moc'): continue # paranoid check if d in mocfiles: error("paranoia owns") continue # process that base.moc only once mocfiles.append(d) # find the extension (performed only when the .cpp has changes) base2 = d[:-4] for path in [node.parent] + self.generator.env['INC_PATHS']: tree.rescan(path) vals = getattr(Options.options, 'qt_header_ext', '') or MOC_H for ex in vals: h_node = path.find_resource(base2 + ex) if h_node: break else: continue break else: raise Utils.WafError("no header found for %s which is a moc file" % str(d)) m_node = h_node.change_ext('.moc') tree.node_deps[(self.inputs[0].parent.id, self.env.variant(), m_node.name)] = h_node # create the task task = Task.TaskBase.classes['moc'](self.env, normal=0) task.set_inputs(h_node) task.set_outputs(m_node) generator = tree.generator generator.outstanding.insert(0, task) generator.total += 1 moctasks.append(task) # remove raw deps except the moc files to save space (optimization) tmp_lst = tree.raw_deps[self.unique_id()] = mocfiles # look at the file inputs, it is set right above lst = tree.node_deps.get(self.unique_id(), ()) for d in lst: name = d.name if name.endswith('.moc'): task = Task.TaskBase.classes['moc'](self.env, normal=0) task.set_inputs(tree.node_deps[(self.inputs[0].parent.id, self.env.variant(), name)]) # 1st element in a tuple task.set_outputs(d) generator = tree.generator generator.outstanding.insert(0, task) generator.total += 1 moctasks.append(task) # simple scheduler dependency: run the moc task before others self.run_after = moctasks self.moc_done = 1 run = Task.TaskBase.classes['cxx'].__dict__['run'] def translation_update(task): outs = [a.abspath(task.env) for a in task.outputs] outs = " ".join(outs) lupdate = task.env['QT_LUPDATE'] for x in task.inputs: file = x.abspath(task.env) cmd = "%s %s -ts %s" % (lupdate, file, outs) Utils.pprint('BLUE', cmd) task.generator.bld.exec_command(cmd) class XMLHandler(ContentHandler): def __init__(self): self.buf = [] self.files = [] def startElement(self, name, attrs): if name == 'file': self.buf = [] def endElement(self, name): if name == 'file': self.files.append(''.join(self.buf)) def characters(self, cars): self.buf.append(cars) def scan(self): "add the dependency on the files referenced in the qrc" node = self.inputs[0] parser = make_parser() curHandler = XMLHandler() parser.setContentHandler(curHandler) fi = open(self.inputs[0].abspath(self.env)) parser.parse(fi) fi.close() nodes = [] names = [] root = self.inputs[0].parent for x in curHandler.files: nd = root.find_resource(x) if nd: nodes.append(nd) else: names.append(x) return (nodes, names) @extension(EXT_RCC) def create_rcc_task(self, node): "hook for rcc files" rcnode = node.change_ext('_rc.cpp') rcctask = self.create_task('rcc', node, rcnode) cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o')) self.compiled_tasks.append(cpptask) return cpptask @extension(EXT_UI) def create_uic_task(self, node): "hook for uic tasks" uictask = self.create_task('ui4', node) uictask.outputs = [self.path.find_or_declare(self.env['ui_PATTERN'] % node.name[:-3])] return uictask class qt4_taskgen(cxx.cxx_taskgen): def __init__(self, *k, **kw): cxx.cxx_taskgen.__init__(self, *k, **kw) self.features.append('qt4') @extension('.ts') def add_lang(self, node): """add all the .ts file into self.lang""" self.lang = self.to_list(getattr(self, 'lang', [])) + [node] @feature('qt4') @after('apply_link') def apply_qt4(self): if getattr(self, 'lang', None): update = getattr(self, 'update', None) lst=[] trans=[] for l in self.to_list(self.lang): if not isinstance(l, Node.Node): l = self.path.find_resource(l+'.ts') t = self.create_task('ts2qm', l, l.change_ext('.qm')) lst.append(t.outputs[0]) if update: trans.append(t.inputs[0]) trans_qt4 = getattr(Options.options, 'trans_qt4', False) if update and trans_qt4: # we need the cpp files given, except the rcc task we create after # FIXME may be broken u = Task.TaskCmd(translation_update, self.env, 2) u.inputs = [a.inputs[0] for a in self.compiled_tasks] u.outputs = trans if getattr(self, 'langname', None): t = Task.TaskBase.classes['qm2rcc'](self.env) t.set_inputs(lst) t.set_outputs(self.path.find_or_declare(self.langname+'.qrc')) t.path = self.path k = create_rcc_task(self, t.outputs[0]) self.link_task.inputs.append(k.outputs[0]) self.env.append_value('MOC_FLAGS', self.env._CXXDEFFLAGS) self.env.append_value('MOC_FLAGS', self.env._CXXINCFLAGS) @extension(EXT_QT4) def cxx_hook(self, node): # create the compilation task: cpp or cc try: obj_ext = self.obj_ext except AttributeError: obj_ext = '_%d.o' % self.idx task = self.create_task('qxx', node, node.change_ext(obj_ext)) self.compiled_tasks.append(task) return task def process_qm2rcc(task): outfile = task.outputs[0].abspath(task.env) f = open(outfile, 'w') f.write('\n\n') for k in task.inputs: f.write(' ') #f.write(k.name) f.write(k.path_to_parent(task.path)) f.write('\n') f.write('\n') f.close() b = Task.simple_task_type b('moc', '${QT_MOC} ${MOC_FLAGS} ${SRC} ${MOC_ST} ${TGT}', color='BLUE', vars=['QT_MOC', 'MOC_FLAGS'], shell=False) 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) cls.scan = scan b('ui4', '${QT_UIC} ${SRC} -o ${TGT}', color='BLUE', before='cxx moc qxx_task', shell=False) b('ts2qm', '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}', color='BLUE', before='qm2rcc', shell=False) Task.task_type_from_func('qm2rcc', vars=[], func=process_qm2rcc, color='BLUE', before='rcc', after='ts2qm') def detect_qt4(conf): env = conf.env opt = Options.options qtdir = getattr(opt, 'qtdir', '') qtbin = getattr(opt, 'qtbin', '') qtlibs = getattr(opt, 'qtlibs', '') useframework = getattr(opt, 'use_qt4_osxframework', True) paths = [] # the path to qmake has been given explicitely if qtbin: paths = [qtbin] # the qt directory has been given - we deduce the qt binary path if not qtdir: qtdir = conf.environ.get('QT4_ROOT', '') qtbin = os.path.join(qtdir, 'bin') paths = [qtbin] # no qtdir, look in the path and in /usr/local/Trolltech if not qtdir: paths = os.environ.get('PATH', '').split(os.pathsep) paths.append('/usr/share/qt4/bin/') try: lst = os.listdir('/usr/local/Trolltech/') except OSError: pass else: if lst: lst.sort() lst.reverse() # keep the highest version qtdir = '/usr/local/Trolltech/%s/' % lst[0] qtbin = os.path.join(qtdir, 'bin') paths.append(qtbin) # at the end, try to find qmake in the paths given # keep the one with the highest version cand = None prev_ver = ['4', '0', '0'] for qmk in ['qmake-qt4', 'qmake4', 'qmake']: qmake = conf.find_program(qmk, path_list=paths) if qmake: try: version = Utils.cmd_output([qmake, '-query', 'QT_VERSION']).strip() except ValueError: pass else: if version: new_ver = version.split('.') if new_ver > prev_ver: cand = qmake prev_ver = new_ver if cand: qmake = cand else: conf.fatal('could not find qmake for qt4') conf.env.QMAKE = qmake qtincludes = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_HEADERS']).strip() qtdir = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_PREFIX']).strip() + os.sep qtbin = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_BINS']).strip() + os.sep if not qtlibs: try: qtlibs = Utils.cmd_output([qmake, '-query', 'QT_INSTALL_LIBS']).strip() + os.sep except ValueError: qtlibs = os.path.join(qtdir, 'lib') def find_bin(lst, var): for f in lst: ret = conf.find_program(f, path_list=paths) if ret: env[var]=ret break vars = "QtCore QtGui QtUiTools QtNetwork QtOpenGL QtSql QtSvg QtTest QtXml QtWebKit Qt3Support".split() find_bin(['uic-qt3', 'uic3'], 'QT_UIC3') find_bin(['uic-qt4', 'uic'], 'QT_UIC') if not env['QT_UIC']: conf.fatal('cannot find the uic compiler for qt4') try: version = Utils.cmd_output(env['QT_UIC'] + " -version 2>&1").strip() except ValueError: conf.fatal('your uic compiler is for qt3, add uic for qt4 to your path') version = version.replace('Qt User Interface Compiler ','') version = version.replace('User Interface Compiler for Qt', '') if version.find(" 3.") != -1: conf.check_message('uic version', '(too old)', 0, option='(%s)'%version) sys.exit(1) conf.check_message('uic version', '', 1, option='(%s)'%version) find_bin(['moc-qt4', 'moc'], 'QT_MOC') find_bin(['rcc'], 'QT_RCC') find_bin(['lrelease-qt4', 'lrelease'], 'QT_LRELEASE') find_bin(['lupdate-qt4', 'lupdate'], 'QT_LUPDATE') env['UIC3_ST']= '%s -o %s' env['UIC_ST'] = '%s -o %s' env['MOC_ST'] = '-o' env['ui_PATTERN'] = 'ui_%s.h' env['QT_LRELEASE_FLAGS'] = ['-silent'] vars_debug = [a+'_debug' for a in vars] try: conf.find_program('pkg-config', var='pkgconfig', path_list=paths, mandatory=True) except Configure.ConfigurationError: for lib in vars_debug+vars: uselib = lib.upper() d = (lib.find('_debug') > 0) and 'd' or '' # original author seems to prefer static to shared libraries for (pat, kind) in ((conf.env.staticlib_PATTERN, 'STATIC'), (conf.env.shlib_PATTERN, '')): conf.check_message_1('Checking for %s %s' % (lib, kind)) for ext in ['', '4']: path = os.path.join(qtlibs, pat % (lib + d + ext)) if os.path.exists(path): env.append_unique(kind + 'LIB_' + uselib, lib + d + ext) conf.check_message_2('ok ' + path, 'GREEN') break path = os.path.join(qtbin, pat % (lib + d + ext)) if os.path.exists(path): env.append_unique(kind + 'LIB_' + uselib, lib + d + ext) conf.check_message_2('ok ' + path, 'GREEN') break else: conf.check_message_2('not found', 'YELLOW') continue break env.append_unique('LIBPATH_' + uselib, qtlibs) env.append_unique('CPPPATH_' + uselib, qtincludes) env.append_unique('CPPPATH_' + uselib, qtincludes + os.sep + lib) else: for i in vars_debug+vars: try: conf.check_cfg(package=i, args='--cflags --libs --silence-errors', path=conf.env.pkgconfig) except ValueError: pass # the libpaths are set nicely, unfortunately they make really long command-lines # remove the qtcore ones from qtgui, etc def process_lib(vars_, coreval): for d in vars_: var = d.upper() if var == 'QTCORE': continue value = env['LIBPATH_'+var] if value: core = env[coreval] accu = [] for lib in value: if lib in core: continue accu.append(lib) env['LIBPATH_'+var] = accu process_lib(vars, 'LIBPATH_QTCORE') process_lib(vars_debug, 'LIBPATH_QTCORE_DEBUG') # rpath if wanted want_rpath = getattr(Options.options, 'want_rpath', 1) if want_rpath: def process_rpath(vars_, coreval): for d in vars_: var = d.upper() value = env['LIBPATH_'+var] if value: core = env[coreval] accu = [] for lib in value: if var != 'QTCORE': if lib in core: continue accu.append('-Wl,--rpath='+lib) env['RPATH_'+var] = accu process_rpath(vars, 'LIBPATH_QTCORE') process_rpath(vars_debug, 'LIBPATH_QTCORE_DEBUG') env['QTLOCALE'] = str(env['PREFIX'])+'/share/locale' def detect(conf): detect_qt4(conf) def set_options(opt): opt.add_option('--want-rpath', type='int', default=1, dest='want_rpath', help='set rpath to 1 or 0 [Default 1]') opt.add_option('--header-ext', type='string', default='', help='header extension for moc files', dest='qt_header_ext') for i in 'qtdir qtbin qtlibs'.split(): opt.add_option('--'+i, type='string', default='', dest=i) if sys.platform == "darwin": 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) opt.add_option('--translate', action="store_true", help="collect translation strings", dest="trans_qt4", default=False)