Add a QT tool. (Christoph Wiedemann)
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 24 Jul 2003 08:11:18 +0000 (08:11 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 24 Jul 2003 08:11:18 +0000 (08:11 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@748 fdb21ef1-2011-0410-befe-b5e4ea1792b1

14 files changed:
bin/files
doc/man/scons.1
etc/TestSCons.py
src/CHANGES.txt
src/engine/MANIFEST.in
src/engine/SCons/Builder.py
src/engine/SCons/BuilderTests.py
src/engine/SCons/Defaults.py
src/engine/SCons/Node/NodeTests.py
src/engine/SCons/Node/__init__.py
src/engine/SCons/Tool/qt.py [new file with mode: 0644]
test/QT.py [new file with mode: 0644]
test/QTFLAGS.py [new file with mode: 0644]
test/import.py

index c4b19a385f406b91893a432aa36ad8ffbcb9c671..baeeb14b2dbaed752c442b23355c16f366cd63a9 100644 (file)
--- a/bin/files
+++ b/bin/files
@@ -70,6 +70,7 @@
 ./SCons/Tool/nasm.py
 ./SCons/Tool/pdflatex.py
 ./SCons/Tool/pdftex.py
+./SCons/Tool/qt.py
 ./SCons/Tool/rmic.py
 ./SCons/Tool/sgiar.py
 ./SCons/Tool/sgicc.py
index 49fe6fde0d6faf792624cf4fd7b423f2f4a3aeae..29b2bb2e35287cba0a7008171fb7110a0a8aab91 100644 (file)
@@ -895,6 +895,7 @@ msvs
 nasm
 pdflatex
 pdftex
+qt
 rmic
 sgiar
 sgicc
@@ -1351,7 +1352,8 @@ env.CFile(target = 'bar', source = 'bar.y')
 .EE
 
 .IP CXXFile
-Builds a C++ source file given a lex (.ll) or yacc (.yy) input file.
+Builds a C++ source file given a lex (.ll), yacc (.yy)
+or uic (.ui) input file.
 The suffix specified by the $CXXFILESUFFIX construction variable
 (.cc by default)
 is automatically added to the target
@@ -3068,6 +3070,105 @@ The prefix used for PostScript file names.
 .IP PSSUFFIX
 The prefix used for PostScript file names.
 
+.IP QTDIR
+The qt tool tries to take this from os.environ.
+It also initializes all QT_*
+construction variables listed below.
+(Note that all paths are constructed
+with python's os.path.join() method, but listed here with the '/' seperator
+for easier reading.)
+In addition, the construction environment
+variables CPPPATH, LIBPATH, LIBS, PROGEMITTER, SHLIBEMITTER and LIBEMITTER
+are modified. Because the build-performance is affected when using this tool,
+you have to explicitly specify it at Environment creation:
+.ES
+Environment(tools=['default','qt']).
+.EE
+.IP
+You may want to use
+.B Configure
+to verify that the qt support really works.
+The qt tool supports the following operations:
+
+.B Automatic moc file generation from header files.
+You do not have to specify moc files explicitly, the tool does it for you.
+However, there are a few preconditions to do so: Your header file must have
+the same filebase as your implementation file and must stay in the same
+directory. It must have one of the suffixes .h, .hpp, .H, .hxx, .hh.
+
+.B Automatic moc file generation from cxx files.
+As stated in the qt documentation,
+include the moc file at the end of the cxx file.
+Note that you have to include the file, which is generated by the
+QT_MOCNAMEGENERATOR function. If you are using BuildDir, you may need to
+specify duplicate=1.
+
+.B Automatic handling of .ui files.
+The implementation files generated from .ui files are handled much the same
+as yacc or lex files. Because there are also generated headers, you may
+need to specify duplicate=1 in calls to BuildDir.
+
+.IP QT_LIB
+Default value is 'qt'. You may want to set this to 'qt-mt'
+
+.IP QT_MOC
+Default value is '$QTDIR/bin/moc'.
+
+.IP QT_UIC
+Default value is '$QTDIR/bin/uic'.
+
+.IP QT_UICIMPLFLAGS
+Default value is ''. These flags are passed to uic, when creating a cxx
+file from a .ui file.
+
+.IP QT_UICDECLFLAGS
+Default value is ''. These flags are passed to uic, when creating a a h
+file from a .ui file.
+
+.IP QT_MOCFROMHFLAGS
+Default value is ''. These flags are passed to moc, when moccing a header
+file.
+
+.IP QT_MOCFROMCPPFLAGS
+Default value is '-i'. These flags are passed to moc, when moccing a
+cpp file.
+
+.IP QT_HSUFFIX
+Default value is '.h'. Suffix of headers generated with uic.
+
+.IP QT_UISUFFIX
+Default value is '.ui'. Suffix of designer files.
+
+.IP QT_UIHSUFFIX
+Default value is '.ui.h'.
+
+.IP QT_MOCNAMEGENERATOR
+Three-argument function, which generates names of moc output files.
+This is the most flexible way to support the huge number of conventions
+for this type of files. The arguments are the
+.I filebase
+, which is the file to be moc'd without path and extension, the
+.I src_suffix
+, which is the extension of the file to be moc'd and the environment
+.I env
+The default value maps 'myfile.myext' to 'moc_myfile.$CXXFILESUFFIX':
+
+.ES
+lambda filebase, src_suffix, env: 'moc_' + filebase + env['CXXFILESUFFIX']
+.EE
+
+.IP QT_UICIMPLCOM
+Command to generate cxx files from .ui files.
+
+.IP QT_UICDECLCOM
+Command to generate header files from .ui files.
+
+.IP QT_MOCFROMHCOM
+Command to generate a moc file from a header.
+
+.IP QT_MOCFROMCXXCOM
+Command to generate a moc file from a cpp file.
+
 .IP RANLIB
 The archive indexer.
 
index 786b394f1d9bad5d58ffc61893053d045dc8950c..59b0647612b072f26a0423424593fce1d67bfcba 100644 (file)
@@ -264,3 +264,22 @@ class TestSCons(TestCmd.TestCmd):
         kw['arguments'] = arguments
         kw['stdout'] = self.wrap_stdout(build_str = s)
         apply(self.run, [], kw)
+
+    def not_up_to_date(self, options = None, arguments = None, **kw):
+        """Asserts that none of the targets listed in arguments is
+        up to date, but does not make any assumptions on other targets.
+        This function is most useful in conjunction with the -n option.
+        """
+        s = ""
+        for  arg in string.split(arguments):
+            s = s + "(?!scons: `%s' is up to date.)" % arg
+            if options:
+                arguments = options + " " + arguments
+        kw['arguments'] = arguments
+        stdout = self.wrap_stdout(build_str="("+s+"[^\n]*\n)*")
+        stdout = string.replace(stdout,'\n','\\n')
+        stdout = string.replace(stdout,'.','\\.')
+        old_match_func = self.match_func
+        self.match_func = TestCmd.match_re_dotall
+        apply(self.run, [], kw)
+        self.match_func = old_match_func
index 4b8b3303bf84840017d6cb427a8e53c75a8532cd..1b21aa88c65ab3e41d8f648114c5327ea62175db 100644 (file)
@@ -68,7 +68,7 @@ RELEASE 0.XX - XXX
   - When the -debug=pdb option is specified, use pdb.Pdb().runcall() to
     call pdb directly, don't call Python recursively.
 
-  From Christoph Wiedemann
+  From Christoph Wiedemann:
 
   - Have the g++ Tool actually use g++ in preference to c++.
 
@@ -81,6 +81,8 @@ RELEASE 0.XX - XXX
 
   - Avoid SCons hanging when a piped command has a lot of output to read.
 
+  - Add QT support for preprocessing .ui files into .c files.
+
 
 
 RELEASE 0.90 - Wed, 25 Jun 2003 14:24:52 -0500
index 53c8c8a3a83fc9a102adaada603089255f133d0f..3cfcff85711adc73b0ef0e7daa10964b49d6f573 100644 (file)
@@ -84,6 +84,7 @@ SCons/Tool/pdflatex.py
 SCons/Tool/pdftex.py
 SCons/Tool/Perforce.py
 SCons/Tool/PharLapCommon.py
+SCons/Tool/qt.py
 SCons/Tool/RCS.py
 SCons/Tool/rmic.py
 SCons/Tool/SCCS.py
index 2d5828adf3398913e7fff9a2a77175ce29d0c375..2bcd9935bfd47aaa62392d97c82ae4aedb85d721 100644 (file)
@@ -89,11 +89,25 @@ class DictCmdGenerator:
         if not ext:
             raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source))))
         try:
-            # XXX Do we need to perform Environment substitution
-            # on the keys of action_dict before looking it up?
             return self.action_dict[ext]
         except KeyError:
-            raise UserError("While building `%s': Don't know how to build a file with suffix %s." % (repr(map(str, target)), repr(ext)))
+            # Before raising the user error, try to perform Environment
+            # substitution on the keys of action_dict.
+            s_dict = {}
+            for (k,v) in self.action_dict.items():
+                s_k = env.subst(k)
+                if s_dict.has_key(s_k):
+                    # XXX Note that we do only raise errors, when variables
+                    # point to the same suffix. If one suffix is a
+                    # literal and a variable suffix contains this literal
+                    # we don't raise an error (cause the literal 'wins')
+                    raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (s_dict[s_k][0], k, s_k))
+                s_dict[s_k] = (k,v)
+            try:
+                return s_dict[ext][1]
+            except KeyError:
+                raise UserError("While building `%s': Don't know how to build a file with suffix %s." % (repr(map(str, target)), repr(ext)))
+
     def __cmp__(self, other):
         return cmp(self.action_dict, other.action_dict)
 
index d486e70c624c10444a442a846e3ea37065dc65d4..e8a619983b88f54e53d515d785da0aa20708801f 100644 (file)
@@ -73,6 +73,8 @@ class Environment:
         try:
             if s[0] == '$':
                 return self.d.get(s[1:], '')
+            if s[1] == '$':
+                return s[0] + self.d.get(s[2:], '')
         except IndexError:
             pass
         return self.d.get(s, s)
@@ -444,8 +446,12 @@ class BuilderTestCase(unittest.TestCase):
         def func_action(target, source, env):
             return 0
         
+        env['BAR_SUFFIX'] = '.BAR2'
+        env['FOO_SUFFIX'] = '.FOO2'
         builder = SCons.Builder.Builder(action={ '.foo' : func_action,
-                                                 '.bar' : func_action })
+                                                 '.bar' : func_action,
+                                                 '$BAR_SUFFIX' : func_action,
+                                                 '$FOO_SUFFIX' : func_action })
         
         assert isinstance(builder, SCons.Builder.CompositeBuilder)
         assert isinstance(builder.action, SCons.Action.CommandGeneratorAction)
@@ -465,6 +471,27 @@ class BuilderTestCase(unittest.TestCase):
         match = str(e) == "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo"
         assert match, e
 
+        tgt = builder(env, target='test4', source=['test4.BAR2'])
+        assert isinstance(tgt.builder, SCons.Builder.BuilderBase)
+        try:
+            tgt.build()
+            flag = 1
+        except SCons.Errors.UserError, e:
+            print e
+            flag = 0
+        assert flag, "It should be possible to define actions in composite builders using variables."
+        env['FOO_SUFFIX'] = '.BAR2'
+        builder.add_action('$NEW_SUFFIX', func_action)
+        flag = 0
+        tgt = builder(env, target='test5', source=['test5.BAR2'])
+        try:
+            tgt.build()
+        except SCons.Errors.UserError:
+            flag = 1
+        assert flag, "UserError should be thrown when we build targets with ambigous suffixes."
+        del env.d['FOO_SUFFIX']
+        del env.d['BAR_SUFFIX']
+
         foo_bld = SCons.Builder.Builder(action = 'a-foo',
                                         src_suffix = '.ina',
                                         suffix = '.foo')
index aa0fbd94d9660bd6cdb3d87d25df153a8cfe89b2..2cef6fa93d25403b3dd682a0a86095098e91735a 100644 (file)
@@ -170,6 +170,7 @@ def SharedObject():
 ProgScan = SCons.Scanner.Prog.ProgScan()
 
 StaticLibrary = SCons.Builder.Builder(action=[ StaticCheck, "$ARCOM" ],
+                                      emitter='$LIBEMITTER',
                                       prefix = '$LIBPREFIX',
                                       suffix = '$LIBSUFFIX',
                                       src_suffix = '$OBJSUFFIX',
index 2d6e0bce293c32c3a41e76042a9ab0b06b8bd113..f4d4845412ed7fddaeba39ed508f237084a69ef9 100644 (file)
@@ -630,6 +630,44 @@ class NodeTestCase(unittest.TestCase):
         assert s.called
         assert node.implicit == [d], node.implicit
 
+        # Check that scanning a node with some stored implicit
+        # dependencies resets internal attributes appropriately
+        # if the stored dependencies need recalculation.
+        class StoredNode(MyNode):
+            def get_stored_implicit(self):
+                return ['implicit1', 'implicit2']
+
+        class NotCurrent:
+            def current(self, node, sig):
+                return None
+            def bsig(self, node):
+                return 0
+
+        import SCons.Sig
+
+        save_default_calc = SCons.Sig.default_calc
+        save_implicit_cache = SCons.Node.implicit_cache
+        save_implicit_deps_changed = SCons.Node.implicit_deps_changed
+        save_implicit_deps_unchanged = SCons.Node.implicit_deps_unchanged
+        SCons.Sig.default_calc = NotCurrent()
+        SCons.Node.implicit_cache = 1
+        SCons.Node.implicit_deps_changed = None
+        SCons.Node.implicit_deps_unchanged = None
+        try:
+            sn = StoredNode("eee")
+            sn._children = ['fake']
+            sn.target_scanner = s
+
+            sn.scan()
+
+            assert sn.implicit == [], sn.implicit
+            assert not hasattr(sn, '_children'), "unexpected _children attribute"
+        finally:
+            SCons.Sig.default_calc = save_default_calc
+            SCons.Node.implicit_cache = save_implicit_cache
+            SCons.Node.implicit_deps_changed = save_implicit_deps_changed
+            SCons.Node.implicit_deps_unchanged = save_implicit_deps_unchanged
+
     def test_scanner_key(self):
         """Test that a scanner_key() method exists"""
         assert SCons.Node.Node().scanner_key() == None
index 1a80d5af1c966a6adfddb854c24288df313113fc..66ffe64da547c207f860f086820b5c1bc7c17fc8 100644 (file)
@@ -360,6 +360,7 @@ class Node:
             return
         self.implicit = []
         self.implicit_dict = {}
+        self._children_reset()
         if not self.has_builder():
             return
 
@@ -377,6 +378,7 @@ class Node:
                     # and the bsig:
                     self.implicit = []
                     self.implicit_dict = {}
+                    self._children_reset()
                     self.del_bsig()
 
         build_env = self.get_build_env()
@@ -594,16 +596,19 @@ class Node:
                 added = 1
             c.parents[self] = 1
         if added:
-            try:
-                delattr(self, '_children')
-            except AttributeError:
-                pass
+            self._children_reset()
 
     def add_wkid(self, wkid):
         """Add a node to the list of kids waiting to be evaluated"""
         if self.wkids != None:
             self.wkids.append(wkid)
 
+    def _children_reset(self):
+        try:
+            delattr(self, '_children')
+        except AttributeError:
+            pass
+
     def children(self, scan=1):
         """Return a list of the node's direct children, minus those
         that are ignored by this node."""
diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py
new file mode 100644 (file)
index 0000000..1febc31
--- /dev/null
@@ -0,0 +1,214 @@
+"""SCons.Tool.qt
+
+Tool-specific initialization for qt.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os.path
+import re
+
+import SCons.Tool
+import SCons.Defaults
+
+header_extensions = (".h", ".H", ".hxx", ".hpp", ".hh")
+
+class _Automoc:
+    """
+    Callable class, which works as an emitter for Programs, SharedLibraries and
+    StaticLibraries.
+    """
+
+    def __init__(self, objBuilder,uicDeclBuild,mocFromHBld,mocFromCppBld):
+        self.objBuilder = objBuilder
+        self.uicDeclBld = uicDeclBuild
+        self.mocFromHBld = mocFromHBld
+        self.mocFromCppBld = mocFromCppBld
+        
+    def __call__(self, target, source, env):
+        """
+        Smart autoscan function. Gets the list of objects for the Program
+        or Lib. Adds objects and builders for the special qt files.
+        """
+        # To make the following work, we assume that we stay in the
+        # root directory
+        old_os_cwd = os.getcwd()
+        old_fs_cwd = SCons.Node.FS.default_fs.getcwd()
+        SCons.Node.FS.default_fs.chdir(SCons.Node.FS.default_fs.Dir('#'),
+                                       change_os_dir=1)
+
+        # a regular expression for the Q_OBJECT macro
+        # currently fails, when Q_OBJECT is in comment (e.g. /* Q_OBJECT */)
+        q_object_search = re.compile(r'\sQ_OBJECT[\s;]')
+        # out_sources contains at least all sources for the Library or Prog
+        out_sources = source[:]
+        for s in source:
+            prefix, suffix = os.path.splitext(str(s))
+            # Nodes for header (h) / moc file (moc_cpp) / cpp file (cpp)
+            # and ui.h file (ui_h)
+            cpp = s.sources[0]
+            ui = None
+            if cpp.sources != None and len(cpp.sources) > 0:
+                src_src_suffix = os.path.splitext(str(cpp.sources[0]))[1]
+                if src_src_suffix == env.subst('$QT_UISUFFIX'):
+                    ui = cpp.sources[0]
+            
+            src_prefix, src_suffix = os.path.splitext(str(cpp.srcnode()))
+            h=None
+            for h_ext in header_extensions:
+                if os.path.exists(src_prefix + h_ext):
+                    h = SCons.Node.FS.default_fs.File(prefix + h_ext)
+
+            if ui:
+                # file built from .ui file -> build also header from .ui
+                h = self.uicDeclBld(env, prefix, ui)
+                env.Depends(cpp, h)
+                ui_h_suff = env.subst('$QT_UIHSUFFIX')
+                if os.path.exists(src_prefix + ui_h_suff):
+                    # if a .ui.h file exists, we need to specify the dependecy ...
+                    ui_h = SCons.Node.FS.default_fs.File(prefix + ui_h_suff)
+                    env.Depends(cpp, ui_h)
+            if (h and q_object_search.search(h.get_contents())) or ui:
+                # h file with the Q_OBJECT macro found -> add moc_cpp
+                dir,base = os.path.split(prefix)
+                src_ext = os.path.splitext(str(h))[1]
+                moc_cpp = SCons.Node.FS.default_fs.File(os.path.join(dir, 
+                    env['QT_MOCNAMEGENERATOR'](base, src_ext, env)))
+                moc_o = self.objBuilder(source=moc_cpp)
+                out_sources.append(moc_o)
+                self.objBuilder(moc_o, moc_cpp)
+                self.mocFromHBld(env, moc_cpp, h)
+                moc_cpp.target_scanner = SCons.Defaults.CScan
+            if cpp and q_object_search.search(cpp.get_contents()):
+                # cpp file with Q_OBJECT macro found -> add moc
+                # (to be included in cpp)
+                dir,base = os.path.split(prefix)
+                src_ext = os.path.splitext(str(cpp))[1]
+                moc = SCons.Node.FS.default_fs.File(os.path.join(dir, 
+                    env['QT_MOCNAMEGENERATOR'](base, src_ext, env)))
+                self.mocFromCppBld(env, moc, cpp)
+                env.Ignore(moc, moc)
+                moc.source_scanner = SCons.Defaults.CScan
+
+        os.chdir(old_os_cwd)
+        SCons.Node.FS.default_fs.chdir(old_fs_cwd)
+        return (target, out_sources)
+
+def _detect(env):
+    """Not really safe, but fast method to detect the QT library"""
+    QTDIR = None
+    if not QTDIR:
+        QTDIR = env.get('QTDIR',None)
+    if not QTDIR:
+        QTDIR = os.environ.get('QTDIR',None)
+    if not QTDIR:
+        moc = env.Detect('moc')
+        if moc:
+            QTDIR = dirname(dirname(moc))
+        else:
+            QTDIR = None
+    env['QTDIR'] = QTDIR
+    return QTDIR
+
+def generate(env):
+    """Add Builders and construction variables for qt to an Environment."""
+    _detect(env)
+    env['QT_MOC']    = os.path.join('$QTDIR','bin','moc')
+    env['QT_UIC']    = os.path.join('$QTDIR','bin','uic')
+    env['QT_LIB']    = 'qt'
+
+    # Some QT specific flags. I don't expect someone wants to
+    # manipulate those ...
+    env['QT_UICIMPLFLAGS'] = ''
+    env['QT_UICDECLFLAGS'] = ''
+    env['QT_MOCFROMHFLAGS'] = ''
+    env['QT_MOCFROMCXXFLAGS'] = '-i'
+
+    # Suffixes for the headers / sources to generate
+    env['QT_HSUFFIX'] = '.h'
+    env['QT_UISUFFIX'] = '.ui'
+    env['QT_UIHSUFFIX'] = '.ui.h'
+    env['QT_MOCNAMEGENERATOR'] = \
+         lambda x, src_suffix, env: 'moc_' + x + env.get('CXXFILESUFFIX','.cc')
+
+    # Commands for the qt support ...
+    # command to generate implementation (cpp) file from a .ui file
+    env['QT_UICIMPLCOM'] = ('$QT_UIC $QT_UICIMPLFLAGS -impl '
+                            '${TARGETS[0].filebase}$QT_HSUFFIX '
+                            '-o $TARGET $SOURCES')
+    # command to generate declaration (h) file from a .ui file
+    env['QT_UICDECLCOM'] = ('$QT_UIC $QT_UICDECLFLAGS '
+                            '-o ${TARGETS[0].base}$QT_HSUFFIX $SOURCES')
+    # command to generate meta object information for a class declarated
+    # in a header
+    env['QT_MOCFROMHCOM'] = '$QT_MOC $QT_MOCFROMHFLAGS -o $TARGET $SOURCE'
+    # command to generate meta object information for a class declatazed
+    # in a cpp file
+    env['QT_MOCFROMCXXCOM'] = '$QT_MOC $QT_MOCFROMCXXFLAGS -o $TARGET $SOURCE'
+
+    # ... and the corresponding builders
+    uicDeclBld = SCons.Builder.Builder(action='$QT_UICDECLCOM',
+                                       src_suffix='$QT_UISUFFIX',
+                                       suffix='$QT_HSUFFIX')
+    mocFromHBld = SCons.Builder.Builder(action='$QT_MOCFROMHCOM',
+                                        src_suffix='$QT_HSUFFIX',
+                                        suffix='$QT_MOCSUFFIX')
+    mocFromCppBld = SCons.Builder.Builder(action='$QT_MOCFROMCXXCOM',
+                                          src_suffix='$QT_CXXSUFFIX',
+                                          suffix='$QT_MOCSUFFIX')
+
+    # we use CXXFile to generate .cpp files from .ui files
+    c_file, cxx_file = SCons.Tool.createCFileBuilders(env)
+    cxx_file.add_action('$QT_UISUFFIX', '$QT_UICIMPLCOM')
+
+    # We use the emitters of Program / StaticLibrary / SharedLibrary
+    # to produce almost all builders except .cpp from .ui
+    try:
+        static = env.StaticObject
+    except AttributeError:
+        static = SCons.Defaults.StaticObject
+    try:
+        shared = env.SharedObject
+    except AttributeError:
+        shared = SCons.Defaults.SharedObject
+    env['PROGEMITTER'] = _Automoc(static,
+                                  uicDeclBld,mocFromHBld,mocFromCppBld)
+    env['SHLIBEMITTER'] = _Automoc(shared,
+                                   uicDeclBld,mocFromHBld,mocFromCppBld)
+    env['LIBEMITTER'] = _Automoc(static,
+                                 uicDeclBld,mocFromHBld,mocFromCppBld)
+    # Of course, we need to link against the qt libraries
+    env.Append(CPPPATH=os.path.join('$QTDIR', 'include'))
+    env.Append(LIBPATH=os.path.join('$QTDIR', 'lib')) 
+    env.Append(LIBS='$QT_LIB')
+
+def exists(env):
+    return _detect(env)
diff --git a/test/QT.py b/test/QT.py
new file mode 100644 (file)
index 0000000..ec64d35
--- /dev/null
@@ -0,0 +1,377 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Testing the 'qt' tool, i.e. support for .ui files and automatic
+generation of qt's moc files.
+"""
+
+import TestSCons
+import os.path
+
+python = TestSCons.python
+_exe = TestSCons._exe
+_dll = TestSCons._dll
+lib_ = TestSCons.lib_
+
+test = TestSCons.TestSCons()
+
+test.subdir( 'qt', ['qt', 'bin'], ['qt', 'include'], ['qt', 'lib'] )
+
+# create a dummy qt installation
+
+test.write(['qt', 'bin', 'mymoc.py'], """
+import getopt
+import sys
+import string
+import re
+cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
+output = None
+impl = 0
+opt_string = ''
+for opt, arg in cmd_opts:
+    if opt == '-o': output = open(arg, 'wb')
+    elif opt == '-i': impl = 1
+    else: opt_string = opt_string + ' ' + opt
+for a in args:
+    contents = open(a, 'rb').read()
+    subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
+    if impl:
+        contents = re.sub( r'#include.*', '', contents )
+    output.write(string.replace(contents, 'Q_OBJECT', subst))
+output.close()
+sys.exit(0)
+""" )
+
+test.write(['qt', 'bin', 'myuic.py'], """
+import sys
+import string
+output_arg = 0
+impl_arg = 0
+impl = None
+source = None
+for arg in sys.argv[1:]:
+    if output_arg:
+        output = open(arg, 'wb')
+        output_arg = 0
+    elif impl_arg:
+        impl = arg
+        impl_arg = 0
+    elif arg == "-o":
+        output_arg = 1
+    elif arg == "-impl":
+        impl_arg = 1
+    else:
+        if source:
+            sys.exit(1)
+        source = open(arg, 'rb')
+if impl:
+    output.write( '#include "' + impl + '"\\n' )
+else:
+    output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
+output.close()
+sys.exit(0)
+""" )
+
+test.write(['qt', 'include', 'my_qobject.h'], r"""
+#define Q_OBJECT ;
+void my_qt_symbol(const char *arg);
+""")
+
+test.write(['qt', 'lib', 'my_qobject.cpp'], r"""
+#include "../include/my_qobject.h"
+#include <stdio.h>
+void my_qt_symbol(const char *arg) {
+  printf( arg );
+}
+""")
+
+test.write(['qt', 'lib', 'SConstruct'], r"""
+env = Environment()
+env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
+""")
+
+test.run(chdir=test.workpath('qt','lib'), arguments = '.')
+
+QT = test.workpath('qt')
+QT_LIB = 'myqt'
+QT_MOC = '%s %s' % (python, test.workpath('qt','bin','mymoc.py'))
+QT_UIC = '%s %s' % (python, test.workpath('qt','bin','myuic.py'))
+
+# 3 test cases with 3 different operation modes
+
+def createSConstruct(test,place):
+    test.write(place, """
+env = Environment(QTDIR='%s',
+                  QT_LIB='%s',
+                  QT_MOC = '%s',
+                  QT_UIC = '%s',
+                  tools=['default','qt'])
+if ARGUMENTS.get('build_dir', 0):
+    if ARGUMENTS.get('chdir', 0):
+        SConscriptChdir(1)
+    else:
+        SConscriptChdir(0)
+    BuildDir('build', '.', duplicate=1)
+    sconscript = Dir('build').File('SConscript')
+else:
+    sconscript = File('SConscript')
+Export("env")
+SConscript( sconscript )
+""" % (QT, QT_LIB, QT_MOC, QT_UIC))
+
+test.subdir( 'work1', 'work2', 'work3', 'work4' )
+
+# 1. create a moc file from a header file.
+
+aaa_exe = 'aaa' + _exe
+moc = 'moc_aaa.cc'
+
+createSConstruct(test, ['work1', 'SConstruct'])
+test.write( ['work1', 'SConscript'], """
+Import("env")
+env.Program(target = 'aaa', source = 'aaa.cpp')
+""")
+
+test.write(['work1', 'aaa.cpp'], r"""
+#include "aaa.h"
+int main() { aaa(); return 0; }
+""")
+
+test.write(['work1', 'aaa.h'], r"""
+#include "my_qobject.h"
+void aaa(void) Q_OBJECT;
+""")
+
+test.run(chdir='work1', arguments = aaa_exe)
+test.up_to_date(chdir='work1', options = '-n', arguments=aaa_exe)
+
+test.up_to_date(chdir='work1', options = '-n', arguments = aaa_exe)
+test.write(['work1', 'aaa.h'], r"""
+/* a change */
+#include "my_qobject.h"
+void aaa(void) Q_OBJECT;
+""")
+test.not_up_to_date(chdir='work1', options='-n', arguments = moc)
+test.run(program = test.workpath('work1', aaa_exe), stdout = 'aaa.h\n')
+
+test.run(chdir='work1',
+         arguments = "build_dir=1 " +
+                     test.workpath('work1', 'build', aaa_exe) )
+test.run(chdir='work1',
+         arguments = "build_dir=1 chdir=1 " +
+                     test.workpath('work1', 'build', aaa_exe) )
+
+test.fail_test( not os.path.exists(test.workpath('work1', 'build', moc)) )
+
+# 2. create .cpp, .h, moc_....cpp from .ui file
+
+aaa_dll = lib_ + 'aaa' + _dll
+moc = 'moc_aaa.cc'
+cpp = 'aaa.cc'
+h = 'aaa.h'
+
+createSConstruct(test, ['work2', 'SConstruct'])
+test.write(['work2', 'SConscript'], """
+Import("env")
+env.SharedLibrary(target = 'aaa', source = ['aaa.ui', 'useit.cpp'])
+""")
+
+test.write(['work2', 'aaa.ui'], r"""
+void aaa(void)
+""")
+
+test.write(['work2', 'useit.cpp'], r"""
+#include "aaa.h"
+void useit() {
+  aaa();
+}
+""")
+
+test.run(chdir='work2', arguments = aaa_dll)
+test.up_to_date(chdir='work2', options='-n',arguments = aaa_dll)
+test.write(['work2', 'aaa.ui'], r"""
+/* a change */
+void aaa(void)
+""")
+test.not_up_to_date(chdir='work2', options = '-n', arguments = moc)
+test.not_up_to_date(chdir='work2', options = '-n', arguments = cpp)
+test.not_up_to_date(chdir='work2', options = '-n', arguments = h)
+test.run(chdir='work2', arguments = aaa_dll)
+test.write(['work2', 'aaa.ui.h'], r"""
+/* test dependency to .ui.h */
+""")
+test.not_up_to_date(chdir='work2', options = '-n', arguments = cpp)
+test.up_to_date(chdir='work2', options = '-n', arguments = h)
+test.up_to_date(chdir='work2', options = '-n', arguments = moc)
+
+test.run(chdir='work2',
+         arguments = "build_dir=1 " +
+                     test.workpath('work2', 'build', aaa_dll) )
+test.run(chdir='work2',
+         arguments = "build_dir=1 chdir=1 " +
+                     test.workpath('work2', 'build', aaa_dll) )
+
+test.fail_test(not os.path.exists(test.workpath('work2','build',moc)) or
+               not os.path.exists(test.workpath('work2','build',cpp)) or
+               not os.path.exists(test.workpath('work2','build',h)))
+
+# 3. create a moc file from a cpp file
+
+lib_aaa = lib_ + 'aaa.a'
+moc = 'moc_aaa.cc'
+
+createSConstruct(test, ['work3', 'SConstruct'])
+test.write(['work3', 'SConscript'], """
+Import("env")
+env.StaticLibrary(target = '%s', source = ['aaa.cpp','useit.cpp'])
+""" % lib_aaa)
+
+test.write(['work3', 'aaa.h'], r"""
+void aaa(void);
+""")
+
+test.write(['work3', 'aaa.cpp'], r"""
+#include "my_qobject.h"
+void aaa(void) Q_OBJECT
+#include "%s"
+""" % moc)
+
+test.write(['work3', 'useit.cpp'], r"""
+#include "aaa.h"
+void useit() {
+  aaa();
+}
+""")
+
+test.run(chdir='work3', arguments = lib_aaa)
+test.up_to_date(chdir='work3', options = '-n', arguments = lib_aaa)
+test.write(['work3', 'aaa.cpp'], r"""
+#include "my_qobject.h"
+/* a change */
+void aaa(void) Q_OBJECT
+#include "%s"
+""" % moc)
+test.not_up_to_date(chdir='work3', options = '-n', arguments = moc)
+
+test.run(chdir='work3',
+         arguments = "build_dir=1 " +
+                     test.workpath('work3', 'build', lib_aaa) )
+test.run(chdir='work3',
+         arguments = "build_dir=1 chdir=1 " +
+                     test.workpath('work3', 'build', lib_aaa) )
+
+test.fail_test(not os.path.exists(test.workpath('work3', 'build', moc)))
+
+
+# look if qt is installed, and try out all builders
+
+if os.environ.get('QTDIR', None):
+
+    test.write( ['work4', 'SConstruct'],"""
+import os
+env = Environment(tools=['default','qt'], CXXFILESUFFIX=".cpp")
+env.Program('test_realqt', ['mocFromCpp.cpp',
+                            'mocFromH.cpp',
+                            'anUiFile.ui',
+                            'main.cpp'])
+""")
+
+    test.write( ['work4', 'mocFromCpp.h'],"""
+void mocFromCpp();
+""")
+
+    test.write( ['work4', 'mocFromCpp.cpp'],"""
+#include <qobject.h>
+#include "mocFromCpp.h"
+class MyClass1 : public QObject {
+  Q_OBJECT
+  public:
+  MyClass1() : QObject() {};
+  public slots:
+  void myslot() {};
+};
+void mocFromCpp() {
+  MyClass1 myclass;
+}
+#include "moc_mocFromCpp.cpp"
+""")
+
+    test.write( ['work4', 'mocFromH.h'],"""
+#include <qobject.h>
+class MyClass2 : public QObject {
+  Q_OBJECT;
+  public:
+  MyClass2();
+  public slots:
+  void myslot();
+};
+void mocFromH();
+""")
+    
+    test.write( ['work4', 'mocFromH.cpp'],"""
+#include "mocFromH.h"
+    
+MyClass2::MyClass2() : QObject() {}
+void MyClass2::myslot() {}
+void mocFromH() {
+  MyClass2 myclass;
+}
+""")
+    
+    test.write( ['work4', 'anUiFile.ui'],"""
+<!DOCTYPE UI><UI>
+<class>MyWidget</class>
+<widget>
+    <class>QWidget</class>
+    <property name="name">
+        <cstring>MyWidget</cstring>
+    </property>
+    <property name="caption">
+        <string>MyWidget</string>
+    </property>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
+""")
+
+    test.write( ['work4', 'main.cpp'], """
+#include "mocFromCpp.h"
+#include "mocFromH.h"
+#include "anUiFile.h"
+int main() {
+  mocFromCpp();
+  mocFromH();
+  MyWidget mywidget();
+}
+""")
+
+    test.run(chdir='work4', arguments="test_realqt" + _exe)
+else:
+    print "Could not find QT, skipping test(s)."
+
+
+test.pass_test()
diff --git a/test/QTFLAGS.py b/test/QTFLAGS.py
new file mode 100644 (file)
index 0000000..c61fb9c
--- /dev/null
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Testing the configuration mechanisms of the 'qt' tool.
+"""
+
+import TestSCons
+import os.path
+
+python = TestSCons.python
+_exe = TestSCons._exe
+
+test = TestSCons.TestSCons()
+
+test.subdir( 'qt', ['qt', 'bin'], ['qt', 'include'], ['qt', 'lib'] )
+
+# create a dummy qt installation
+
+test.write(['qt', 'bin', 'mymoc.py'], """
+import getopt
+import sys
+import string
+import re
+cmd_opts, args = getopt.getopt(sys.argv[1:], 'wzio:', [])
+output = None
+impl = 0
+opt_string = ''
+for opt, arg in cmd_opts:
+    if opt == '-o': output = open(arg, 'wb')
+    elif opt == '-i': impl = 1
+    else: opt_string = opt_string + ' ' + opt
+output.write( "/* mymoc.py%s */\\n" % opt_string)
+for a in args:
+    contents = open(a, 'rb').read()
+    subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
+    if impl:
+        contents = re.sub( r'#include.*', '', contents )
+    output.write(string.replace(contents, 'Q_OBJECT', subst))
+output.close()
+sys.exit(0)
+""" )
+
+test.write(['qt', 'bin', 'myuic.py'], """
+import sys
+import string
+output_arg = 0
+impl_arg = 0
+impl = None
+source = None
+opt_string = ''
+for arg in sys.argv[1:]:
+    if output_arg:
+        output = open(arg, 'wb')
+        output_arg = 0
+    elif impl_arg:
+        impl = arg
+        impl_arg = 0
+    elif arg == "-o":
+        output_arg = 1
+    elif arg == "-impl":
+        impl_arg = 1
+    elif arg[0:1] == "-":
+        opt_string = opt_string + ' ' + arg
+    else:
+        if source:
+            sys.exit(1)
+        source = open(arg, 'rb')
+output.write("/* myuic.py%s */\\n" % opt_string)
+if impl:
+    output.write( '#include "' + impl + '"\\n' )
+else:
+    output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
+output.close()
+sys.exit(0)
+""" )
+
+test.write(['qt', 'include', 'my_qobject.h'], r"""
+#define Q_OBJECT ;
+void my_qt_symbol(const char *arg);
+""")
+
+test.write(['qt', 'lib', 'my_qobject.cpp'], r"""
+#include "../include/my_qobject.h"
+#include <stdio.h>
+void my_qt_symbol(const char *arg) {
+  printf( arg );
+}
+""")
+
+test.write(['qt', 'lib', 'SConstruct'], r"""
+env = Environment()
+env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
+""")
+
+test.run(chdir=test.workpath('qt','lib'), arguments = '.')
+
+QT = test.workpath('qt')
+QT_LIB = 'myqt'
+QT_MOC = '%s %s' % (python, test.workpath('qt','bin','mymoc.py'))
+QT_UIC = '%s %s' % (python, test.workpath('qt','bin','myuic.py'))
+
+# 3 test cases with 3 different operation modes
+
+def createSConstruct(test,place,overrides):
+    test.write(place, """
+env = Environment(QTDIR='%s',
+                  QT_LIB='%s',
+                  QT_MOC = '%s',
+                  QT_UIC = '%s',
+                  %s
+                  tools=['default','qt'])
+if ARGUMENTS.get('build_dir', 0):
+    if ARGUMENTS.get('chdir', 0):
+        SConscriptChdir(1)
+    else:
+        SConscriptChdir(0)
+    BuildDir('build', '.', duplicate=1)
+    sconscript = Dir('build').File('SConscript')
+else:
+    sconscript = File('SConscript')
+Export("env")
+SConscript( sconscript )
+""" % (QT, QT_LIB, QT_MOC, QT_UIC, overrides))
+
+
+createSConstruct(test, ['SConstruct'],
+                 """QT_UICIMPLFLAGS='-x',
+                    QT_UICDECLFLAGS='-y',
+                    QT_MOCFROMHFLAGS='-z',
+                    QT_MOCFROMCXXFLAGS='-i -w',
+                    QT_HSUFFIX='.hpp',
+                    QT_MOCNAMEGENERATOR=lambda x,src_suffix,env: x + '.moc.cpp',
+                    QT_UISUFFIX='.des',
+                    QT_UIHSUFFUX='.des.hpp',
+                    CXXFILESUFFIX='.cpp',""")
+test.write('SConscript',"""
+Import("env")
+env.Program('mytest', ['mocFromH.cpp',
+                       'mocFromCpp.cpp',
+                       'an_ui_file.des',
+                       'another_ui_file.des',
+                       'main.cpp'])
+""")
+
+test.write('mocFromH.hpp', """
+#include "my_qobject.h"
+void mocFromH() Q_OBJECT
+""")
+
+test.write('mocFromH.cpp', """
+#include "mocFromH.hpp"
+""")
+
+test.write('mocFromCpp.cpp', """
+#include "my_qobject.h"
+void mocFromCpp() Q_OBJECT
+#include "mocFromCpp.moc.cpp"
+""")
+
+test.write('an_ui_file.des', """
+void an_ui_file()
+""")
+
+test.write('another_ui_file.des', """
+void another_ui_file()
+""")
+
+test.write('another_ui_file.desc.hpp', """
+/* just a dependency checker */
+""")
+
+test.write('main.cpp', """
+#include "mocFromH.hpp"
+#include "an_ui_file.hpp"
+#include "another_ui_file.hpp"
+void mocFromCpp();
+
+int main() {
+  mocFromH();
+  mocFromCpp();
+  an_ui_file();
+  another_ui_file();
+}
+""")
+
+test.run( arguments = "mytest" + _exe )
+
+def _existAll( test, files ):
+    return reduce(lambda x,y: x and y,
+                  map(os.path.exists,map(test.workpath, files)))
+                       
+test.fail_test(not _existAll(test, ['mocFromH.moc.cpp',
+                                    'mocFromCpp.moc.cpp',
+                                    'an_ui_file.cpp',
+                                    'an_ui_file.hpp',
+                                    'an_ui_file.moc.cpp',
+                                    'another_ui_file.cpp',
+                                    'another_ui_file.hpp',
+                                    'another_ui_file.moc.cpp']))
+
+def _flagTest(test,fileToContentsStart):
+    import string
+    for f,c in fileToContentsStart.items():
+        if string.find(test.read(f), c) != 0:
+            return 1
+    return 0
+
+test.fail_test(_flagTest(test, {'mocFromH.moc.cpp':'/* mymoc.py -z */',
+                                'mocFromCpp.moc.cpp':'/* mymoc.py -w */',
+                                'an_ui_file.cpp':'/* myuic.py -x */',
+                                'an_ui_file.hpp':'/* myuic.py -y */',
+                                'an_ui_file.moc.cpp':'/* mymoc.py -z */'}))
+
+test.pass_test()
index d66f8e5cd691b26b34051ff384a1d6b91e8947be..99aa120cc3d02b22501a1a0e18ac8137098318bb 100644 (file)
@@ -89,6 +89,7 @@ tools = [
     'pdftex',
     'Perforce',
     'RCS',
+    'qt',
     'rmic',
     'SCCS',
     'sgiar',