1
2"""SCons.Tool.qt4
3
4Tool-specific initialization for Qt4.
5
6There normally shouldn't be any need to import this module directly.
7It will usually be imported through the generic SCons.Tool.Tool()
8selection method.
9
10"""
11
12#
13# Copyright (c) 2001-7,2010 The SCons Foundation
14#
15# Permission is hereby granted, free of charge, to any person obtaining
16# a copy of this software and associated documentation files (the
17# "Software"), to deal in the Software without restriction, including
18# without limitation the rights to use, copy, modify, merge, publish,
19# distribute, sublicense, and/or sell copies of the Software, and to
20# permit persons to whom the Software is furnished to do so, subject to
21# the following conditions:
22#
23# The above copyright notice and this permission notice shall be included
24# in all copies or substantial portions of the Software.
25#
26# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
27# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
28# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33#
34
35# Additionally in Photivo :
36# Changes done in/for Photivo are largely traceable due to the
37# use of pt-Prefixes.
38
39################################################################################
40##
41## Photivo
42##
43## Copyright (C) 2013 Jos De Laender <jos@de-laender.be>
44##
45## This file is part of Photivo.
46##
47## Photivo is free software: you can redistribute it and/or modify
48## it under the terms of the GNU General Public License version 3
49## as published by the Free Software Foundation.
50##
51## Photivo is distributed in the hope that it will be useful,
52## but WITHOUT ANY WARRANTY; without even the implied warranty of
53## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
54## GNU General Public License for more details.
55##
56## You should have received a copy of the GNU General Public License
57## along with Photivo.  If not, see <http://www.gnu.org/licenses/>.
58##
59################################################################################
60
61import os.path
62import re
63
64import SCons.Action
65import SCons.Builder
66import SCons.Defaults
67import SCons.Scanner
68import SCons.Tool
69import SCons.Util
70
71import sys
72
73class ToolQt4Warning(SCons.Warnings.Warning):
74    pass
75
76class GeneratedMocFileNotIncluded(ToolQt4Warning):
77    pass
78
79class QtdirNotFound(ToolQt4Warning):
80    pass
81
82SCons.Warnings.enableWarningClass(ToolQt4Warning)
83
84try:
85    sorted
86except NameError:
87    # Pre-2.4 Python has no sorted() function.
88    #
89    # The pre-2.4 Python list.sort() method does not support
90    # list.sort(key=) nor list.sort(reverse=) keyword arguments, so
91    # we must implement the functionality of those keyword arguments
92    # by hand instead of passing them to list.sort().
93    def sorted(iterable, cmp=None, key=None, reverse=0):
94        if key is not None:
95            result = [(key(x), x) for x in iterable]
96        else:
97            result = iterable[:]
98        if cmp is None:
99            # Pre-2.3 Python does not support list.sort(None).
100            result.sort()
101        else:
102            result.sort(cmp)
103        if key is not None:
104            result = [t1 for t0,t1 in result]
105        if reverse:
106            result.reverse()
107        return result
108
109qrcinclude_re = re.compile(r'<file[^>]*>([^<]*)</file>', re.M)
110
111def transformToWinePath(path) :
112    return os.popen('winepath -w "%s"'%path).read().strip().replace('\\','/')
113
114header_extensions = [".h", ".hxx", ".hpp", ".hh"]
115if SCons.Util.case_sensitive_suffixes('.h', '.H'):
116    header_extensions.append('.H')
117# TODO: The following two lines will work when integrated back to SCons
118# TODO: Meanwhile the third line will do the work
119#cplusplus = __import__('c++', globals(), locals(), [])
120#cxx_suffixes = cplusplus.CXXSuffixes
121cxx_suffixes = [".c", ".cxx", ".cpp", ".cc"]
122
123def checkMocIncluded(target, source, env):
124    moc = target[0]
125    cpp = source[0]
126    # looks like cpp.includes is cleared before the build stage :-(
127    # not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/
128    path = SCons.Defaults.CScan.path_function(env, moc.cwd)
129    includes = SCons.Defaults.CScan(cpp, env, path)
130    if not moc in includes:
131        SCons.Warnings.warn(
132            GeneratedMocFileNotIncluded,
133            "Generated moc file '%s' is not included by '%s'" %
134            (str(moc), str(cpp)))
135
136def find_file(filename, paths, node_factory):
137    for dir in paths:
138        node = node_factory(filename, dir)
139        if node.rexists():
140            return node
141    return None
142
143class _Automoc:
144    """
145    Callable class, which works as an emitter for Programs, SharedLibraries and
146    StaticLibraries.
147    """
148
149    def __init__(self, objBuilderName):
150        self.objBuilderName = objBuilderName
151        # some regular expressions:
152        # Q_OBJECT detection
153        self.qo_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]')
154        # cxx and c comment 'eater'
155        self.ccomment = re.compile(r'/\*(.*?)\*/',re.S)
156        self.cxxcomment = re.compile(r'//.*$',re.M)
157        # we also allow Q_OBJECT in a literal string
158        self.literal_qobject = re.compile(r'"[^\n]*Q_OBJECT[^\n]*"')
159
160    def create_automoc_options(self, env):
161        """
162        Create a dictionary with variables related to Automocing,
163        based on the current environment.
164        Is executed once in the __call__ routine.
165        """
166        moc_options = {'auto_scan' : True,
167                       'auto_scan_strategy' : 0,
168                       'gobble_comments' : 0,
169                       'debug' : 0,
170                       'auto_cpppath' : True,
171                       'cpppaths' : []}
172        try:
173            if int(env.subst('$QT4_AUTOSCAN')) == 0:
174                moc_options['auto_scan'] = False
175        except ValueError:
176            pass
177        try:
178            moc_options['auto_scan_strategy'] = int(env.subst('$QT4_AUTOSCAN_STRATEGY'))
179        except ValueError:
180            pass
181        try:
182            moc_options['gobble_comments'] = int(env.subst('$QT4_GOBBLECOMMENTS'))
183        except ValueError:
184            pass
185        try:
186            moc_options['debug'] = int(env.subst('$QT4_DEBUG'))
187        except ValueError:
188            pass
189        try:
190            if int(env.subst('$QT4_AUTOMOC_SCANCPPPATH')) == 0:
191                moc_options['auto_cpppath'] = False
192        except ValueError:
193            pass
194        if moc_options['auto_cpppath']:
195            paths = env.get('QT4_AUTOMOC_CPPPATH', [])
196            if not paths:
197                paths = env.get('CPPPATH', [])
198            moc_options['cpppaths'].extend(paths)
199
200        return moc_options
201
202    def __automoc_strategy_simple(self, env, moc_options,
203                                  cpp, cpp_contents, out_sources):
204        """
205        Default Automoc strategy (Q_OBJECT driven): detect a header file
206        (alongside the current cpp/cxx) that contains a Q_OBJECT
207        macro...and MOC it.
208        If a Q_OBJECT macro is also found in the cpp/cxx itself,
209        it gets MOCed too.
210        """
211
212        h=None
213        for h_ext in header_extensions:
214            # try to find the header file in the corresponding source
215            # directory
216            hname = self.splitext(cpp.name)[0] + h_ext
217            h = find_file(hname, [cpp.get_dir()]+moc_options['cpppaths'], env.File)
218            if h:
219                if moc_options['debug']:
220                    print "scons: qt4: Scanning '%s' (header of '%s')" % (str(h), str(cpp))
221                h_contents = h.get_contents()
222                if moc_options['gobble_comments']:
223                    h_contents = self.ccomment.sub('', h_contents)
224                    h_contents = self.cxxcomment.sub('', h_contents)
225                h_contents = self.literal_qobject.sub('""', h_contents)
226                break
227        if not h and moc_options['debug']:
228            print "scons: qt4: no header for '%s'." % (str(cpp))
229        if h and self.qo_search.search(h_contents):
230            # h file with the Q_OBJECT macro found -> add moc_cpp
231            moc_cpp = env.Moc4(h)
232            moc_o = self.objBuilder(moc_cpp)
233            out_sources.extend(moc_o)
234            if moc_options['debug']:
235                print "scons: qt4: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(moc_cpp))
236        if cpp and self.qo_search.search(cpp_contents):
237            # cpp file with Q_OBJECT macro found -> add moc
238            # (to be included in cpp)
239            moc = env.Moc4(cpp)
240            env.Ignore(moc, moc)
241            if moc_options['debug']:
242                print "scons: qt4: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(cpp), str(moc))
243
244    def __automoc_strategy_include_driven(self, env, moc_options,
245                                          cpp, cpp_contents, out_sources):
246        """
247        Automoc strategy #1 (include driven): searches for "include"
248        statements of MOCed files in the current cpp/cxx file.
249        This strategy tries to add support for the compilation
250        of the qtsolutions...
251        """
252        if self.splitext(str(cpp))[1] in cxx_suffixes:
253            added = False
254            h_moc = "%s%s%s" % (env.subst('$QT4_XMOCHPREFIX'),
255                                self.splitext(cpp.name)[0],
256                                env.subst('$QT4_XMOCHSUFFIX'))
257            cxx_moc = "%s%s%s" % (env.subst('$QT4_XMOCCXXPREFIX'),
258                                  self.splitext(cpp.name)[0],
259                                  env.subst('$QT4_XMOCCXXSUFFIX'))
260            inc_h_moc = r'#include\s+"%s"' % h_moc
261            inc_cxx_moc = r'#include\s+"%s"' % cxx_moc
262
263            # Search for special includes in qtsolutions style
264            if cpp and re.search(inc_h_moc, cpp_contents):
265                # cpp file with #include directive for a MOCed header found -> add moc
266
267                # Try to find header file
268                h=None
269                hname=""
270                for h_ext in header_extensions:
271                    # Try to find the header file in the
272                    # corresponding source directory
273                    hname = self.splitext(cpp.name)[0] + h_ext
274                    h = find_file(hname, [cpp.get_dir()]+moc_options['cpppaths'], env.File)
275                    if h:
276                        if moc_options['debug']:
277                            print "scons: qt4: Scanning '%s' (header of '%s')" % (str(h), str(cpp))
278                        h_contents = h.get_contents()
279                        if moc_options['gobble_comments']:
280                            h_contents = self.ccomment.sub('', h_contents)
281                            h_contents = self.cxxcomment.sub('', h_contents)
282                        h_contents = self.literal_qobject.sub('""', h_contents)
283                        break
284                if not h and moc_options['debug']:
285                    print "scons: qt4: no header for '%s'." % (str(cpp))
286                if h and self.qo_search.search(h_contents):
287                    # h file with the Q_OBJECT macro found -> add moc_cpp
288                    moc_cpp = env.XMoc4(h)
289                    env.Ignore(moc_cpp, moc_cpp)
290                    added = True
291                    # Removing file from list of sources, because it is not to be
292                    # compiled but simply included by the cpp/cxx file.
293                    for idx, s in enumerate(out_sources):
294                        if hasattr(s, "sources") and len(s.sources) > 0:
295                            if str(s.sources[0]) == h_moc:
296                                out_sources.pop(idx)
297                                break
298                    if moc_options['debug']:
299                        print "scons: qt4: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(h_moc))
300                else:
301                    if moc_options['debug']:
302                        print "scons: qt4: found no Q_OBJECT macro in '%s', but a moc'ed version '%s' gets included in '%s'" % (str(h), inc_h_moc, cpp.name)
303
304            if cpp and re.search(inc_cxx_moc, cpp_contents):
305                # cpp file with #include directive for a MOCed cxx file found -> add moc
306                if self.qo_search.search(cpp_contents):
307                    moc = env.XMoc4(target=cxx_moc, source=cpp)
308                    env.Ignore(moc, moc)
309                    added = True
310                    if moc_options['debug']:
311                        print "scons: qt4: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(cpp), str(moc))
312                else:
313                    if moc_options['debug']:
314                        print "scons: qt4: found no Q_OBJECT macro in '%s', although a moc'ed version '%s' of itself gets included" % (cpp.name, inc_cxx_moc)
315
316            if not added:
317                # Fallback to default Automoc strategy (Q_OBJECT driven)
318               self.__automoc_strategy_simple(env, moc_options, cpp,
319                                              cpp_contents, out_sources)
320
321    def __call__(self, target, source, env):
322        """
323        Smart autoscan function. Gets the list of objects for the Program
324        or Lib. Adds objects and builders for the special qt4 files.
325        """
326        moc_options = self.create_automoc_options(env)
327
328        # some shortcuts used in the scanner
329        self.splitext = SCons.Util.splitext
330        self.objBuilder = getattr(env, self.objBuilderName)
331
332        # The following is kind of hacky to get builders working properly (FIXME)
333        objBuilderEnv = self.objBuilder.env
334        self.objBuilder.env = env
335        mocBuilderEnv = env.Moc4.env
336        env.Moc4.env = env
337        xMocBuilderEnv = env.XMoc4.env
338        env.XMoc4.env = env
339
340        # make a deep copy for the result; MocH objects will be appended
341        out_sources = source[:]
342
343        for obj in source:
344            if not moc_options['auto_scan']:
345                break
346            if isinstance(obj,basestring):  # big kludge!
347                print "scons: qt4: '%s' MAYBE USING AN OLD SCONS VERSION AND NOT CONVERTED TO 'File'. Discarded." % str(obj)
348                continue
349            if not obj.has_builder():
350                # binary obj file provided
351                if moc_options['debug']:
352                    print "scons: qt4: '%s' seems to be a binary. Discarded." % str(obj)
353                continue
354            cpp = obj.sources[0]
355            if not self.splitext(str(cpp))[1] in cxx_suffixes:
356                if moc_options['debug']:
357                    print "scons: qt4: '%s' is no cxx file. Discarded." % str(cpp)
358                # c or fortran source
359                continue
360            try:
361                cpp_contents = cpp.get_contents()
362                if moc_options['gobble_comments']:
363                    cpp_contents = self.ccomment.sub('', cpp_contents)
364                    cpp_contents = self.cxxcomment.sub('', cpp_contents)
365                cpp_contents = self.literal_qobject.sub('""', cpp_contents)
366            except: continue # may be an still not generated source
367
368            if moc_options['auto_scan_strategy'] == 0:
369                # Default Automoc strategy (Q_OBJECT driven)
370                self.__automoc_strategy_simple(env, moc_options,
371                                               cpp, cpp_contents, out_sources)
372            else:
373                # Automoc strategy #1 (include driven)
374                self.__automoc_strategy_include_driven(env, moc_options,
375                                                       cpp, cpp_contents, out_sources)
376
377        # restore the original env attributes (FIXME)
378        self.objBuilder.env = objBuilderEnv
379        env.Moc4.env = mocBuilderEnv
380        env.XMoc4.env = xMocBuilderEnv
381
382        # We return the set of source entries as sorted sequence, else
383        # the order might accidentally change from one build to another
384        # and trigger unwanted rebuilds. For proper sorting, a key function
385        # has to be specified...FS.Entry (and Base nodes in general) do not
386        # provide a __cmp__, for performance reasons.
387        return (target, sorted(set(out_sources), key=lambda entry : str(entry)))
388
389AutomocShared = _Automoc('SharedObject')
390AutomocStatic = _Automoc('StaticObject')
391
392def _detect(env):
393    """Not really safe, but fast method to detect the Qt4 library"""
394    # TODO: check output of "moc -v" for correct version >= 4.0.0
395    try: return env['QT4DIR']
396    except KeyError: pass
397
398    try: return env['QTDIR']
399    except KeyError: pass
400
401    try: return os.environ['QT4DIR']
402    except KeyError: pass
403
404    try: return os.environ['QTDIR']
405    except KeyError: pass
406
407    moc = env.WhereIs('moc-qt4') or env.WhereIs('moc4') or env.WhereIs('moc')
408    if moc:
409        QT4DIR = os.path.dirname(os.path.dirname(moc))
410        SCons.Warnings.warn(
411            QtdirNotFound,
412            "QT4DIR variable is not defined, using moc executable as a hint (QT4DIR=%s)" % QT4DIR)
413        return QT4DIR
414
415    raise SCons.Errors.StopError(
416        QtdirNotFound,
417        "Could not detect Qt 4 installation")
418    return None
419
420
421def __scanResources(node, env, path, arg):
422    # Helper function for scanning .qrc resource files
423    # I've been careful on providing names relative to the qrc file
424    # If that was not needed this code could be simplified a lot
425    def recursiveFiles(basepath, path) :
426        result = []
427        for item in os.listdir(os.path.join(basepath, path)) :
428            itemPath = os.path.join(path, item)
429            if os.path.isdir(os.path.join(basepath, itemPath)) :
430                result += recursiveFiles(basepath, itemPath)
431            else:
432                result.append(itemPath)
433        return result
434    contents = node.get_contents()
435    includes = qrcinclude_re.findall(contents)
436    qrcpath = os.path.dirname(node.path)
437    dirs = [included for included in includes if os.path.isdir(os.path.join(qrcpath,included))]
438    # dirs need to include files recursively
439    for dir in dirs :
440        includes.remove(dir)
441        includes+=recursiveFiles(qrcpath,dir)
442    return includes
443
444#
445# Scanners
446#
447__qrcscanner = SCons.Scanner.Scanner(name = 'qrcfile',
448    function = __scanResources,
449    argument = None,
450    skeys = ['.qrc'])
451
452#
453# Emitters
454#
455def __qrc_path(head, prefix, tail, suffix):
456    if head:
457        if tail:
458            return os.path.join(head, "%s%s%s" % (prefix, tail, suffix))
459        else:
460            return "%s%s%s" % (prefix, head, suffix)
461    else:
462        return "%s%s%s" % (prefix, tail, suffix)
463def __qrc_emitter(target, source, env):
464    sourceBase, sourceExt = os.path.splitext(SCons.Util.to_String(source[0]))
465    sHead = None
466    sTail = sourceBase
467    if sourceBase:
468        sHead, sTail = os.path.split(sourceBase)
469
470    t = __qrc_path(sHead, env.subst('$QT4_QRCCXXPREFIX'),
471                   sTail, env.subst('$QT4_QRCCXXSUFFIX'))
472
473    return t, source
474
475#
476# Action generators
477#
478def __moc_generator_from_h(source, target, env, for_signature):
479    pass_defines = False
480    try:
481        if int(env.subst('$QT4_CPPDEFINES_PASSTOMOC')) == 1:
482            pass_defines = True
483    except ValueError:
484        pass
485
486    if pass_defines:
487        return '$QT4_MOC $QT4_MOCDEFINES $QT4_MOCFROMHFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE'
488    else:
489        return '$QT4_MOC $QT4_MOCFROMHFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE'
490
491def __moc_generator_from_cxx(source, target, env, for_signature):
492    pass_defines = False
493    try:
494        if int(env.subst('$QT4_CPPDEFINES_PASSTOMOC')) == 1:
495            pass_defines = True
496    except ValueError:
497        pass
498
499    if pass_defines:
500        return ['$QT4_MOC $QT4_MOCDEFINES $QT4_MOCFROMCXXFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE',
501                SCons.Action.Action(checkMocIncluded,None)]
502    else:
503        return ['$QT4_MOC $QT4_MOCFROMCXXFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE',
504                SCons.Action.Action(checkMocIncluded,None)]
505
506def __mocx_generator_from_h(source, target, env, for_signature):
507    pass_defines = False
508    try:
509        if int(env.subst('$QT4_CPPDEFINES_PASSTOMOC')) == 1:
510            pass_defines = True
511    except ValueError:
512        pass
513
514    if pass_defines:
515        return '$QT4_MOC $QT4_MOCDEFINES $QT4_MOCFROMHFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE'
516    else:
517        return '$QT4_MOC $QT4_MOCFROMHFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE'
518
519def __mocx_generator_from_cxx(source, target, env, for_signature):
520    pass_defines = False
521    try:
522        if int(env.subst('$QT4_CPPDEFINES_PASSTOMOC')) == 1:
523            pass_defines = True
524    except ValueError:
525        pass
526
527    if pass_defines:
528        return ['$QT4_MOC $QT4_MOCDEFINES $QT4_MOCFROMCXXFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE',
529                SCons.Action.Action(checkMocIncluded,None)]
530    else:
531        return ['$QT4_MOC $QT4_MOCFROMCXXFLAGS $QT4_MOCINCFLAGS -o $TARGET $SOURCE',
532                SCons.Action.Action(checkMocIncluded,None)]
533
534def __qrc_generator(source, target, env, for_signature):
535    name_defined = False
536    try:
537        if env.subst('$QT4_QRCFLAGS').find('-name') >= 0:
538            name_defined = True
539    except ValueError:
540        pass
541
542    if name_defined:
543        return '$QT4_RCC $QT4_QRCFLAGS $SOURCE -o $TARGET'
544    else:
545        qrc_suffix = env.subst('$QT4_QRCSUFFIX')
546        src = str(source[0])
547        head, tail = os.path.split(src)
548        if tail:
549            src = tail
550        qrc_suffix = env.subst('$QT4_QRCSUFFIX')
551        if src.endswith(qrc_suffix):
552            qrc_stem = src[:-len(qrc_suffix)]
553        else:
554            qrc_stem = src
555        return '$QT4_RCC $QT4_QRCFLAGS -name %s $SOURCE -o $TARGET' % qrc_stem
556
557#
558# Builders
559#
560__ts_builder = SCons.Builder.Builder(
561        action = SCons.Action.Action('$QT4_LUPDATECOM','$QT4_LUPDATECOMSTR'),
562        suffix = '.ts',
563        source_factory = SCons.Node.FS.Entry)
564__qm_builder = SCons.Builder.Builder(
565        action = SCons.Action.Action('$QT4_LRELEASECOM','$QT4_LRELEASECOMSTR'),
566        src_suffix = '.ts',
567        suffix = '.qm')
568__qrc_builder = SCons.Builder.Builder(
569        action = SCons.Action.CommandGeneratorAction(__qrc_generator,
570                                                    {"cmdstr":"QT4_QRCCOMSTR"}),
571        source_scanner = __qrcscanner,
572        src_suffix = '$QT4_QRCSUFFIX',
573        suffix = '$QT4_QRCCXXSUFFIX',
574        prefix = '$QT4_QRCCXXPREFIX',
575        single_source = 1)
576__ex_moc_builder = SCons.Builder.Builder(
577        action = SCons.Action.CommandGeneratorAction(__moc_generator_from_h,
578                                              {"cmdstr":"$QT4_MOCFROMHCOMSTR"}))
579__ex_uic_builder = SCons.Builder.Builder(
580        action = SCons.Action.Action('$QT4_UICCOM', '$QT4_UICCOMSTR'),
581        src_suffix = '.ui')
582
583
584#
585# Wrappers (pseudo-Builders)
586#
587def Ts4(env, target, source=None, *args, **kw):
588    """
589    A pseudo-Builder wrapper around the LUPDATE executable of Qt4.
590        lupdate [options] [source-file|path]... -ts ts-files
591    """
592    if not SCons.Util.is_List(target):
593        target = [target]
594    if not source:
595        source = target[:]
596    if not SCons.Util.is_List(source):
597        source = [source]
598
599    # Check QT4_CLEAN_TS and use NoClean() function
600    clean_ts = False
601    try:
602        if int(env.subst('$QT4_CLEAN_TS')) == 1:
603            clean_ts = True
604    except ValueError:
605        pass
606
607    result = []
608    for t in target:
609        obj = __ts_builder.__call__(env, t, source, **kw)
610        # Prevent deletion of the .ts file, unless explicitly specified
611        if not clean_ts:
612            env.NoClean(obj)
613        # Always make our target "precious", such that it is not deleted
614        # prior to a rebuild
615        env.Precious(obj)
616        # Add to resulting target list
617        result.extend(obj)
618
619    return result
620
621def Qm4(env, target, source=None, *args, **kw):
622    """
623    A pseudo-Builder wrapper around the LRELEASE executable of Qt4.
624        lrelease [options] ts-files [-qm qm-file]
625    """
626    if not SCons.Util.is_List(target):
627        target = [target]
628    if not source:
629        source = target[:]
630    if not SCons.Util.is_List(source):
631        source = [source]
632
633    result = []
634    for t in target:
635        result.extend(__qm_builder.__call__(env, t, source, **kw))
636
637    return result
638
639def Qrc4(env, target, source=None, *args, **kw):
640    """
641    A pseudo-Builder wrapper around the RCC executable of Qt4.
642        rcc [options] qrc-files -o out-file
643    """
644    if not SCons.Util.is_List(target):
645        target = [target]
646    if not source:
647        source = target[:]
648    if not SCons.Util.is_List(source):
649        source = [source]
650
651    result = []
652    for t, s in zip(target, source):
653        result.extend(__qrc_builder.__call__(env, t, s, **kw))
654
655    return result
656
657def ExplicitMoc4(env, target, source, *args, **kw):
658    """
659    A pseudo-Builder wrapper around the MOC executable of Qt4.
660        moc [options] <header-file>
661    """
662    if not SCons.Util.is_List(target):
663        target = [target]
664    if not SCons.Util.is_List(source):
665        source = [source]
666
667    result = []
668    for t in target:
669        # Is it a header or a cxx file?
670        result.extend(__ex_moc_builder.__call__(env, t, source, **kw))
671
672    return result
673
674def ExplicitUic4(env, target, source, *args, **kw):
675    """
676    A pseudo-Builder wrapper around the UIC executable of Qt4.
677        uic [options] <uifile>
678    """
679    if not SCons.Util.is_List(target):
680        target = [target]
681    if not SCons.Util.is_List(source):
682        source = [source]
683
684    result = []
685    for t in target:
686        result.extend(__ex_uic_builder.__call__(env, t, source, **kw))
687
688    return result
689
690def generate(env):
691    """Add Builders and construction variables for qt4 to an Environment."""
692
693    def locateQt4Command(env, command, qtdir) :
694
695        # Take cross into account.
696        ptCrossCommand = env['PT_CROSS'] + command
697
698        # Decorations (linux and msys/mingw/cygwin accept. env in linux)
699        # We issue something like 'ID=DSCONS_UIC /path/to/uic'
700        # This is just for recognizing at command print time.
701        ptDecoration = ''
702        if command == 'moc':
703          ptDecoration = 'ID=DSCONS_MOC'
704        elif command == 'uic' :
705          ptDecoration = 'ID=DSCONS_UIC'
706        elif command == 'rcc' :
707          ptDecoration = 'ID=DSCONS_RCC'
708        elif command == 'lupdate' :
709          ptDecoration = 'ID=DSCONS_LUPDATE'
710        elif command == 'lrelease' :
711          ptDecoration = 'ID=DSCONS_LRELEASE'
712        if sys.platform.startswith('win') :
713	  ptDecoration = ''
714
715        suffixes = [
716            '-qt4',
717            '-qt4.exe',
718            '4',
719            '4.exe',
720            '',
721            '.exe',
722        ]
723        triedPaths = []
724        for suffix in suffixes :
725            fullpath = os.path.join(qtdir,'bin',ptCrossCommand + suffix)
726            if os.access(fullpath, os.X_OK) :
727                return ptDecoration + ' ' + fullpath
728            triedPaths.append(fullpath)
729
730        fullpath = env.Detect([ptCrossCommand+'-qt4',
731                               ptCrossCommand+'4',
732                               ptCrossCommand])
733
734        if not (fullpath is None) : return ptDecoration + ' ' + fullpath
735
736        if command in ('lupdate','lrelease'):
737          print 'Qt4 could not locate \''    + \
738                ptCrossCommand + '\' '       + \
739                '(This might be acceptable)'
740          return None
741
742        raise Exception("Qt4 command '" + command + "' not found. Tried: " + ', '.join(triedPaths))
743
744    CLVar = SCons.Util.CLVar
745    Action = SCons.Action.Action
746    Builder = SCons.Builder.Builder
747
748    env['QT4DIR']  = _detect(env)
749    # TODO: 'Replace' should be 'SetDefault'
750#    env.SetDefault(
751    env.Replace(
752        QT4DIR  = _detect(env),
753        QT4_BINPATH = os.path.join('$QT4DIR', 'bin'),
754        QT4_LIBPATH = os.path.join('$QT4DIR', 'lib'),
755        # TODO: This is not reliable to QT4DIR value changes but needed in order to support '-qt4' variants
756        QT4_MOC = locateQt4Command(env,'moc', env['QT4DIR']),
757        QT4_UIC = locateQt4Command(env,'uic', env['QT4DIR']),
758        QT4_RCC = locateQt4Command(env,'rcc', env['QT4DIR']),
759        QT4_LUPDATE = locateQt4Command(env,'lupdate', env['QT4DIR']),
760        QT4_LRELEASE = locateQt4Command(env,'lrelease', env['QT4DIR']),
761
762        QT4_AUTOSCAN = 1, # Should the qt4 tool try to figure out, which sources are to be moc'ed?
763        QT4_AUTOSCAN_STRATEGY = 0, # While scanning for files to moc, should we search for includes in qtsolutions style?
764        QT4_GOBBLECOMMENTS = 0, # If set to 1, comments are removed before scanning cxx/h files.
765        QT4_CPPDEFINES_PASSTOMOC = 1, # If set to 1, all CPPDEFINES get passed to the moc executable.
766        QT4_CLEAN_TS = 0, # If set to 1, translation files (.ts) get cleaned on 'scons -c'
767        QT4_AUTOMOC_SCANCPPPATH = 1, # If set to 1, the CPPPATHs (or QT4_AUTOMOC_CPPPATH) get scanned for moc'able files
768        QT4_AUTOMOC_CPPPATH = [], # Alternative paths that get scanned for moc files
769
770        # Some Qt4 specific flags. I don't expect someone wants to
771        # manipulate those ...
772        QT4_UICFLAGS = CLVar(''),
773        QT4_MOCFROMHFLAGS = CLVar(''),
774        QT4_MOCFROMCXXFLAGS = CLVar('-i'),
775        QT4_QRCFLAGS = '',
776        QT4_LUPDATEFLAGS = '',
777        QT4_LRELEASEFLAGS = '',
778
779        # suffixes/prefixes for the headers / sources to generate
780        QT4_UISUFFIX = '.ui',
781        QT4_UICDECLPREFIX = 'ui_',
782        QT4_UICDECLSUFFIX = '.h',
783        QT4_MOCINCPREFIX = '-I',
784        QT4_MOCHPREFIX = 'moc_',
785        QT4_MOCHSUFFIX = '$CXXFILESUFFIX',
786        QT4_MOCCXXPREFIX = '',
787        QT4_MOCCXXSUFFIX = '.moc',
788        QT4_QRCSUFFIX = '.qrc',
789        QT4_QRCCXXSUFFIX = '$CXXFILESUFFIX',
790        QT4_QRCCXXPREFIX = 'qrc_',
791        QT4_MOCDEFPREFIX = '-D',
792        QT4_MOCDEFSUFFIX = '',
793        QT4_MOCDEFINES = '${_defines(QT4_MOCDEFPREFIX, CPPDEFINES, QT4_MOCDEFSUFFIX, __env__)}',
794        QT4_MOCCPPPATH = [],
795        QT4_MOCINCFLAGS = '$( ${_concat(QT4_MOCINCPREFIX, QT4_MOCCPPPATH, INCSUFFIX, __env__, RDirs)} $)',
796
797        # Commands for the qt4 support ...
798        QT4_UICCOM = '$QT4_UIC $QT4_UICFLAGS -o $TARGET $SOURCE',
799        QT4_LUPDATECOM = '$QT4_LUPDATE $QT4_LUPDATEFLAGS $SOURCES -ts $TARGET',
800        QT4_LRELEASECOM = '$QT4_LRELEASE $QT4_LRELEASEFLAGS -qm $TARGET $SOURCES',
801
802        # Specialized variables for the Extended Automoc support
803        # (Strategy #1 for qtsolutions)
804        QT4_XMOCHPREFIX = 'moc_',
805        QT4_XMOCHSUFFIX = '.cpp',
806        QT4_XMOCCXXPREFIX = '',
807        QT4_XMOCCXXSUFFIX = '.moc',
808
809        )
810
811    try:
812        env.AddMethod(Ts4, "Ts4")
813        env.AddMethod(Qm4, "Qm4")
814        env.AddMethod(Qrc4, "Qrc4")
815        env.AddMethod(ExplicitMoc4, "ExplicitMoc4")
816        env.AddMethod(ExplicitUic4, "ExplicitUic4")
817    except AttributeError:
818        # Looks like we use a pre-0.98 version of SCons...
819        from SCons.Script.SConscript import SConsEnvironment
820        SConsEnvironment.Ts4 = Ts4
821        SConsEnvironment.Qm4 = Qm4
822        SConsEnvironment.Qrc4 = Qrc4
823        SConsEnvironment.ExplicitMoc4 = ExplicitMoc4
824        SConsEnvironment.ExplicitUic4 = ExplicitUic4
825
826    # Interface builder
827    uic4builder = Builder(
828        action = SCons.Action.Action('$QT4_UICCOM', '$QT4_UICCOMSTR'),
829        src_suffix='$QT4_UISUFFIX',
830        suffix='$QT4_UICDECLSUFFIX',
831        prefix='$QT4_UICDECLPREFIX',
832        single_source = True
833        #TODO: Consider the uiscanner on new scons version
834        )
835    env['BUILDERS']['Uic4'] = uic4builder
836
837    # Metaobject builder
838    mocBld = Builder(action={}, prefix={}, suffix={})
839    for h in header_extensions:
840        act = SCons.Action.CommandGeneratorAction(__moc_generator_from_h,
841                                              {"cmdstr":"$QT4_MOCFROMHCOMSTR"})
842        mocBld.add_action(h, act)
843        mocBld.prefix[h] = '$QT4_MOCHPREFIX'
844        mocBld.suffix[h] = '$QT4_MOCHSUFFIX'
845    for cxx in cxx_suffixes:
846        act = SCons.Action.CommandGeneratorAction(__moc_generator_from_cxx,
847                                             {"cmdstr":"$QT4_MOCFROMCXXCOMSTR"})
848        mocBld.add_action(cxx, act)
849        mocBld.prefix[cxx] = '$QT4_MOCCXXPREFIX'
850        mocBld.suffix[cxx] = '$QT4_MOCCXXSUFFIX'
851    env['BUILDERS']['Moc4'] = mocBld
852
853    # Metaobject builder for the extended auto scan feature
854    # (Strategy #1 for qtsolutions)
855    xMocBld = Builder(action={}, prefix={}, suffix={})
856    for h in header_extensions:
857        act = SCons.Action.CommandGeneratorAction(__mocx_generator_from_h,
858                                             {"cmdstr":"$QT4_MOCFROMHCOMSTR"})
859        xMocBld.add_action(h, act)
860        xMocBld.prefix[h] = '$QT4_XMOCHPREFIX'
861        xMocBld.suffix[h] = '$QT4_XMOCHSUFFIX'
862    for cxx in cxx_suffixes:
863        act = SCons.Action.CommandGeneratorAction(__mocx_generator_from_cxx,
864                                             {"cmdstr":"$QT4_MOCFROMCXXCOMSTR"})
865        xMocBld.add_action(cxx, act)
866        xMocBld.prefix[cxx] = '$QT4_XMOCCXXPREFIX'
867        xMocBld.suffix[cxx] = '$QT4_XMOCCXXSUFFIX'
868    env['BUILDERS']['XMoc4'] = xMocBld
869
870    # Add the Qrc4 action to the CXX file builder (registers the
871    # *.qrc extension with the Environment)
872    cfile_builder, cxxfile_builder = SCons.Tool.createCFileBuilders(env)
873    qrc_act = SCons.Action.CommandGeneratorAction(__qrc_generator,
874                                                  {"cmdstr":"$QT4_QRCCOMSTR"})
875    cxxfile_builder.add_action('$QT4_QRCSUFFIX', qrc_act)
876    cxxfile_builder.add_emitter('$QT4_QRCSUFFIX', __qrc_emitter)
877
878    # We use the emitters of Program / StaticLibrary / SharedLibrary
879    # to scan for moc'able files
880    # We can't refer to the builders directly, we have to fetch them
881    # as Environment attributes because that sets them up to be called
882    # correctly later by our emitter.
883    env.AppendUnique(PROGEMITTER =[AutomocStatic],
884                     SHLIBEMITTER=[AutomocShared],
885                     LIBEMITTER  =[AutomocStatic],
886                    )
887
888    # TODO: Does dbusxml2cpp need an adapter
889    try:
890        env.AddMethod(enable_modules, "EnableQt4Modules")
891    except AttributeError:
892        # Looks like we use a pre-0.98 version of SCons...
893        from SCons.Script.SConscript import SConsEnvironment
894        SConsEnvironment.EnableQt4Modules = enable_modules
895
896def enable_modules(self, modules, debug=False) :
897    import sys
898
899    validModules = [
900        'QtCore',
901        'QtGui',
902        'QtOpenGL',
903        'Qt3Support',
904        'QtAssistant', # deprecated
905        'QtAssistantClient',
906        'QtScript',
907        'QtDBus',
908        'QtSql',
909        'QtSvg',
910        # The next modules have not been tested yet so, please
911        # maybe they require additional work on non Linux platforms
912        'QtNetwork',
913        'QtTest',
914        'QtXml',
915        'QtXmlPatterns',
916        'QtUiTools',
917        'QtDesigner',
918        'QtDesignerComponents',
919        'QtWebKit',
920        'QtHelp',
921        'QtScript',
922        'QtScriptTools',
923        'QtMultimedia',
924        ]
925    pclessModules = [
926# in qt <= 4.3 designer and designerComponents are pcless, on qt4.4 they are not, so removed.
927#        'QtDesigner',
928#        'QtDesignerComponents',
929    ]
930    staticModules = [
931        'QtUiTools',
932    ]
933    invalidModules=[]
934    for module in modules:
935        if module not in validModules :
936            invalidModules.append(module)
937    if invalidModules :
938        raise Exception("Modules %s are not Qt4 modules. Valid Qt4 modules are: %s"% (
939            str(invalidModules),str(validModules)))
940
941    moduleDefines = {
942        'QtScript'   : ['QT_SCRIPT_LIB'],
943        'QtSvg'      : ['QT_SVG_LIB'],
944        'Qt3Support' : ['QT_QT3SUPPORT_LIB','QT3_SUPPORT'],
945        'QtSql'      : ['QT_SQL_LIB'],
946        'QtXml'      : ['QT_XML_LIB'],
947        'QtOpenGL'   : ['QT_OPENGL_LIB'],
948        'QtGui'      : ['QT_GUI_LIB'],
949        'QtNetwork'  : ['QT_NETWORK_LIB'],
950        'QtCore'     : ['QT_CORE_LIB'],
951    }
952    for module in modules :
953        try : self.AppendUnique(CPPDEFINES=moduleDefines[module])
954        except: pass
955    debugSuffix = ''
956    if sys.platform in ["darwin", "linux2", "win32"] :
957        if debug :
958	  if sys.platform in ["win32"] :
959	    debugSuffix = 'd'
960	  else :
961	    debugSuffix = '_debug'
962        for module in modules :
963            if module not in pclessModules : continue
964            self.AppendUnique(LIBS=[module+debugSuffix])
965            self.AppendUnique(LIBPATH=[os.path.join("$QT4DIR","lib")])
966            self.AppendUnique(CPPPATH=[os.path.join("$QT4DIR","include","qt4")])
967            self.AppendUnique(CPPPATH=[os.path.join("$QT4DIR","include","qt4",module)])
968        pcmodules = [module+debugSuffix for module in modules if module not in pclessModules ]
969        if 'QtDBus' in pcmodules:
970            self.AppendUnique(CPPPATH=[os.path.join("$QT4DIR","include","qt4","QtDBus")])
971        if "QtAssistant" in pcmodules:
972            self.AppendUnique(CPPPATH=[os.path.join("$QT4DIR","include","qt4","QtAssistant")])
973        self["QT4_MOCCPPPATH"] = self["CPPPATH"]
974        return
975
976    else :
977      print "CHECK ME. SHOULDN'T"
978      Exit(1)
979
980
981def exists(env):
982    return _detect(env)
983