From 7a3d5b8e06e44fee43fc78d7eb72b8d271022c18 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Thu, 24 Jul 2003 08:11:18 +0000 Subject: Add a QT tool. (Christoph Wiedemann) --- bin/files | 1 + doc/man/scons.1 | 103 +++++++++- etc/TestSCons.py | 19 ++ src/CHANGES.txt | 4 +- src/engine/MANIFEST.in | 1 + src/engine/SCons/Builder.py | 20 +- src/engine/SCons/BuilderTests.py | 29 ++- src/engine/SCons/Defaults.py | 1 + src/engine/SCons/Node/NodeTests.py | 38 ++++ src/engine/SCons/Node/__init__.py | 13 +- src/engine/SCons/Tool/qt.py | 214 +++++++++++++++++++++ test/QT.py | 377 +++++++++++++++++++++++++++++++++++++ test/QTFLAGS.py | 238 +++++++++++++++++++++++ test/import.py | 1 + 14 files changed, 1049 insertions(+), 10 deletions(-) create mode 100644 src/engine/SCons/Tool/qt.py create mode 100644 test/QT.py create mode 100644 test/QTFLAGS.py diff --git a/bin/files b/bin/files index c4b19a3..baeeb14 100644 --- 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 diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 49fe6fd..29b2bb2 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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. diff --git a/etc/TestSCons.py b/etc/TestSCons.py index 786b394..59b0647 100644 --- a/etc/TestSCons.py +++ b/etc/TestSCons.py @@ -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 diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 4b8b330..1b21aa8 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 53c8c8a..3cfcff8 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -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 diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 2d5828a..2bcd993 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -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) diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index d486e70..e8a6199 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -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') diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index aa0fbd9..2cef6fa 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -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', diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 2d6e0bc..f4d4845 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -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 diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 1a80d5a..66ffe64 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -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 index 0000000..1febc31 --- /dev/null +++ b/src/engine/SCons/Tool/qt.py @@ -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 index 0000000..ec64d35 --- /dev/null +++ b/test/QT.py @@ -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 +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 +#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 +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'],""" + +MyWidget + + QWidget + + MyWidget + + + MyWidget + + + + +""") + + 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 index 0000000..c61fb9c --- /dev/null +++ b/test/QTFLAGS.py @@ -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 +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() diff --git a/test/import.py b/test/import.py index d66f8e5..99aa120 100644 --- a/test/import.py +++ b/test/import.py @@ -89,6 +89,7 @@ tools = [ 'pdftex', 'Perforce', 'RCS', + 'qt', 'rmic', 'SCCS', 'sgiar', -- cgit v0.12