From 9509a572ba9977ab49d2fe19475a0f7034618ac6 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Wed, 13 Aug 2003 05:56:44 +0000 Subject: Add SWIG support. --- bin/files | 1 + doc/man/scons.1 | 52 +++++++++++ src/CHANGES.txt | 2 + src/engine/MANIFEST.in | 1 + src/engine/SCons/Builder.py | 27 ++++-- src/engine/SCons/BuilderTests.py | 66 +++++++++++--- src/engine/SCons/Tool/__init__.py | 5 +- src/engine/SCons/Tool/swig.py | 65 ++++++++++++++ test/SWIG.py | 177 ++++++++++++++++++++++++++++++++++++++ test/import.py | 1 + 10 files changed, 372 insertions(+), 25 deletions(-) create mode 100644 src/engine/SCons/Tool/swig.py create mode 100644 test/SWIG.py diff --git a/bin/files b/bin/files index baeeb14..a2facbc 100644 --- a/bin/files +++ b/bin/files @@ -78,6 +78,7 @@ ./SCons/Tool/sunar.py ./SCons/Tool/suncc.py ./SCons/Tool/sunlink.py +./SCons/Tool/swig.py ./SCons/Tool/tar.py ./SCons/Tool/tex.py ./SCons/Tool/yacc.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index b9849c6..aaf6c0f 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -931,6 +931,7 @@ sgilink sunar suncc sunlink +swig tar tex yacc @@ -3498,6 +3499,57 @@ in which the command should be executed. '\".IP SVNFLAGS '\"General options that are passed to Subversion. +.IP SWIG +The scripting language wrapper and interface generator. + +.IP SWIGCFILESUFFIX +The suffix that will be used for intermediate C +source files generated by +the scripting language wrapper and interface generator. +The default value is +.BR _wrap$CFILESUFFIX . +By default, this value is used whenever the +.B -c++ +option is +.I not +specified as part of the +.B SWIGFLAGS +construction variable. + +.IP SWIGCOM +The command line used to call +the scripting language wrapper and interface generator. + +.IP SWIGCXXFILESUFFIX +The suffix that will be used for intermediate C++ +source files generated by +the scripting language wrapper and interface generator. +The default value is +.BR _wrap$CFILESUFFIX . +By default, this value is used whenever the +.B -c++ +option is specified as part of the +.B SWIGFLAGS +construction variable. + +.IP SWIGFLAGS +General options passed to +the scripting language wrapper and interface generator. +This is where you should set +.BR -python , +.BR -perl5 , +.BR -tcl , +or whatever other options you want to specify to SWIG. +If you set the +.B -c++ +option in this variable, +.B scons +will, by default, +generate a C++ intermediate source file +with the extension that is specified as the +.B $CXXFILESUFFIX +variable. + .IP TAR The tar archiver. diff --git a/src/CHANGES.txt b/src/CHANGES.txt index de2d75c..795b29d 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -92,6 +92,8 @@ RELEASE 0.XX - XXX source Node, not by trying to create the right string to pass to arg2nodes(). + - Add support for SWIG. + From Gary Oberbrunner: - Report the target being built in error messages when building diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 3cfcff8..b97cd29 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -95,6 +95,7 @@ SCons/Tool/sunar.py SCons/Tool/suncc.py SCons/Tool/sunlink.py SCons/Tool/Subversion.py +SCons/Tool/swig.py SCons/Tool/tar.py SCons/Tool/tex.py SCons/Tool/yacc.py diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 34f28bc..f7630e6 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -122,8 +122,9 @@ class Selector(UserDict.UserDict): # emitter_dict before giving up. s_dict = {} for (k,v) in self.items(): - s_k = env.subst(k) - s_dict[s_k] = v + if not k is None: + s_k = env.subst(k) + s_dict[s_k] = v try: return s_dict[ext] except KeyError: @@ -132,6 +133,15 @@ class Selector(UserDict.UserDict): except KeyError: return None +class CallableSelector(Selector): + """A callable dictionary that wills, in turn, call the value it + finds if it can.""" + def __call__(self, env, source): + value = Selector.__call__(self, env, source) + if callable(value): + value = value(env, source) + return value + class DictEmitter(Selector): """A callable dictionary that maps file suffixes to emitters. When called, it finds the right emitter in its dictionary for the @@ -198,7 +208,7 @@ def _init_nodes(builder, env, overrides, tlist, slist): raise UserError, "Two different environments were specified for the same target: %s"%str(t) elif t.overrides != overrides: raise UserError, "Two different sets of overrides were specified for the same target: %s"%str(t) - elif builder.scanner and builder.scanner != t.target_scanner: + elif builder.scanner and t.target_scanner and builder.scanner != t.target_scanner: raise UserError, "Two different scanners were specified for the same target: %s"%str(t) if builder.multi: @@ -293,10 +303,10 @@ class BuilderBase: self.action = SCons.Action.Action(action) self.multi = multi if SCons.Util.is_Dict(prefix): - prefix = Selector(prefix) + prefix = CallableSelector(prefix) self.prefix = prefix if SCons.Util.is_Dict(suffix): - suffix = Selector(suffix) + suffix = CallableSelector(suffix) self.suffix = suffix self.env = env self.overrides = overrides @@ -419,7 +429,7 @@ class BuilderBase: return tlist def adjust_suffix(self, suff): - if suff and not suff[0] in [ '.', '$' ]: + if suff and not suff[0] in [ '.', '_', '$' ]: return '.' + suff return suff @@ -553,10 +563,9 @@ class MultiStepBuilder(BuilderBase): src_suffixes = self.src_suffixes(env) for snode in slist: - path, ext = self.splitext(snode.get_abspath()) + base, ext = self.splitext(str(snode)) if sdict.has_key(ext): - src_bld = sdict[ext] - tgt = apply(src_bld, (env, path, snode), kw) + tgt = apply(sdict[ext], (env, None, snode), kw) # Only supply the builder with sources it is capable # of building. if SCons.Util.is_List(tgt): diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 8aafefa..f65230c 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -370,17 +370,26 @@ class BuilderTestCase(unittest.TestCase): my_env['FOO'] = 'abracadabra' assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra" - builder = SCons.Builder.Builder(prefix = {None : 'default-', - '.in' : 'out-', - '.x' : 'y-'}) - tgt = builder(env, source = 'f1') + def my_emit(env, sources): + return env.subst('$EMIT') + my_env = Environment(FOO = '.foo', EMIT = 'emit-') + builder = SCons.Builder.Builder(prefix = {None : 'default-', + '.in' : 'out-', + '.x' : 'y-', + '$FOO' : 'foo-', + '.zzz' : my_emit}) + tgt = builder(my_env, source = 'f1') assert tgt.path == 'default-f1', tgt.path - tgt = builder(env, source = 'f2.c') + tgt = builder(my_env, source = 'f2.c') assert tgt.path == 'default-f2', tgt.path - tgt = builder(env, source = 'f3.in') + tgt = builder(my_env, source = 'f3.in') assert tgt.path == 'out-f3', tgt.path - tgt = builder(env, source = 'f4.x') + tgt = builder(my_env, source = 'f4.x') assert tgt.path == 'y-f4', tgt.path + tgt = builder(my_env, source = 'f5.foo') + assert tgt.path == 'foo-f5', tgt.path + tgt = builder(my_env, source = 'f6.zzz') + assert tgt.path == 'emit-f6', tgt.path def test_src_suffix(self): """Test Builder creation with a specified source file suffix @@ -444,17 +453,26 @@ class BuilderTestCase(unittest.TestCase): my_env['BAR'] = 'presto chango' assert builder.get_suffix(my_env) == "gen_suffix() says presto chango" - builder = SCons.Builder.Builder(suffix = {None : '.default', - '.in' : '.out', - '.x' : '.y'}) - tgt = builder(env, source = 'f1') + def my_emit(env, sources): + return env.subst('$EMIT') + my_env = Environment(BAR = '.bar', EMIT = '.emit') + builder = SCons.Builder.Builder(suffix = {None : '.default', + '.in' : '.out', + '.x' : '.y', + '$BAR' : '.new', + '.zzz' : my_emit}) + tgt = builder(my_env, source = 'f1') assert tgt.path == 'f1.default', tgt.path - tgt = builder(env, source = 'f2.c') + tgt = builder(my_env, source = 'f2.c') assert tgt.path == 'f2.default', tgt.path - tgt = builder(env, source = 'f3.in') + tgt = builder(my_env, source = 'f3.in') assert tgt.path == 'f3.out', tgt.path - tgt = builder(env, source = 'f4.x') + tgt = builder(my_env, source = 'f4.x') assert tgt.path == 'f4.y', tgt.path + tgt = builder(my_env, source = 'f5.bar') + assert tgt.path == 'f5.new', tgt.path + tgt = builder(my_env, source = 'f6.zzz') + assert tgt.path == 'f6.emit', tgt.path def test_ListBuilder(self): """Testing ListBuilder class.""" @@ -513,6 +531,7 @@ class BuilderTestCase(unittest.TestCase): builder2 = SCons.Builder.MultiStepBuilder(action='bar', src_builder = builder1, src_suffix = '.foo') + tgt = builder2(env, target='baz', source=['test.bar', 'test2.foo', 'test3.txt']) assert str(tgt.sources[0]) == 'test.foo', str(tgt.sources[0]) assert str(tgt.sources[0].sources[0]) == 'test.bar', \ @@ -530,6 +549,25 @@ class BuilderTestCase(unittest.TestCase): src_builder = 'xyzzy', src_suffix = '.xyzzy') assert builder3.get_src_builders(Environment()) == [] + + builder4 = SCons.Builder.Builder(action='bld4', + src_suffix='.i', + suffix='_wrap.c') + builder5 = SCons.Builder.MultiStepBuilder(action='bld5', + src_builder=builder4, + suffix='.obj', + src_suffix='.c') + builder6 = SCons.Builder.MultiStepBuilder(action='bld6', + src_builder=builder5, + suffix='.exe', + src_suffix='.obj') + tgt = builder6(env, 'test', 'test.i') + assert str(tgt) == 'test.exe', str(tgt) + assert str(tgt.sources[0]) == 'test_wrap.obj', str(tgt.sources[0]) + assert str(tgt.sources[0].sources[0]) == 'test_wrap.c', \ + str(tgt.sources[0].sources[0]) + assert str(tgt.sources[0].sources[0].sources[0]) == 'test.i', \ + str(tgt.sources[0].sources[0].sources[0]) def test_CompositeBuilder(self): """Testing CompositeBuilder class.""" diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index 2f3f762..3a96d67 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -194,7 +194,7 @@ def createCFileBuilders(env): except KeyError: c_file = SCons.Builder.Builder(action = {}, emitter = {}, - suffix = '$CFILESUFFIX') + suffix = {None:'$CFILESUFFIX'}) env['BUILDERS']['CFile'] = c_file env['CFILESUFFIX'] = '.c' @@ -203,7 +203,7 @@ def createCFileBuilders(env): except KeyError: cxx_file = SCons.Builder.Builder(action = {}, emitter = {}, - suffix = '$CXXFILESUFFIX') + suffix = {None:'$CXXFILESUFFIX'}) env['BUILDERS']['CXXFile'] = cxx_file env['CXXFILESUFFIX'] = '.cc' @@ -310,6 +310,7 @@ def tool_list(platform, env): 'pdflatex', 'pdftex', 'Perforce', 'RCS', 'rmic', 'SCCS', # 'Subversion', + 'swig', 'tar', 'tex', 'yacc', 'zip'], env) diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py new file mode 100644 index 0000000..9dcfad4 --- /dev/null +++ b/src/engine/SCons/Tool/swig.py @@ -0,0 +1,65 @@ +"""SCons.Tool.swig + +Tool-specific initialization for swig. + +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 string + +import SCons.Defaults +import SCons.Tool + +def swigSuffixEmitter(env, source): + if '-c++' in string.split(env.subst("$SWIGFLAGS")): + return '$SWIGCXXFILESUFFIX' + else: + return '$SWIGCFILESUFFIX' + +def generate(env): + """Add Builders and construction variables for swig to an Environment.""" + c_file, cxx_file = SCons.Tool.createCFileBuilders(env) + + c_file.suffix['.i'] = swigSuffixEmitter + cxx_file.suffix['.i'] = swigSuffixEmitter + + c_file.add_action('.i', '$SWIGCOM') + cxx_file.add_action('.i', '$SWIGCOM') + + env['SWIG'] = 'swig' + env['SWIGFLAGS'] = '' + env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX' + env['SWIGCXXFILESUFFIX'] = '_wrap$CXXFILESUFFIX' + env['SWIGCOM'] = '$SWIG $SWIGFLAGS -o $TARGET $SOURCES' + +def exists(env): + return env.Detect(['swig']) diff --git a/test/SWIG.py b/test/SWIG.py new file mode 100644 index 0000000..74e73c4 --- /dev/null +++ b/test/SWIG.py @@ -0,0 +1,177 @@ +#!/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__" + +import os +import string +import sys +import TestSCons + +python = TestSCons.python +_exe = TestSCons._exe +_obj = TestSCons._obj + +test = TestSCons.TestSCons() + + + +test.write('myswig.py', r""" +import getopt +import sys +opts, args = getopt.getopt(sys.argv[1:], 'c:o:') +for opt, arg in opts: + if opt == '-c': pass + elif opt == '-o': out = arg +infile = open(args[0], 'rb') +outfile = open(out, 'wb') +for l in infile.readlines(): + if l[:4] != 'swig': + outfile.write(l) +sys.exit(0) +""") + +test.write('SConstruct', """ +env = Environment(tools=['default', 'swig'], SWIG = r'%s myswig.py') +env.Program(target = 'test1', source = 'test1.i') +env.CFile(target = 'test2', source = 'test2.i') +env.Copy(SWIGFLAGS = '-c++').Program(target = 'test3', source = 'test3.i') +""" % (python)) + +test.write('test1.i', r""" +void +main(int argc, char *argv[]) { + argv[argc++] = "--"; + printf("test1.i\n"); + exit (0); +} +swig +""") + +test.write('test2.i', r"""test2.i +swig +""") + +test.write('test3.i', r""" +#include +#include +void +main(int argc, char *argv[]) { + argv[argc++] = "--"; + printf("test3.i\n"); + exit (0); +} +swig +""") + +test.run(arguments = '.', stderr = None) + +test.run(program = test.workpath('test1' + _exe), stdout = "test1.i\n") +test.fail_test(not os.path.exists(test.workpath('test1_wrap.c'))) +test.fail_test(not os.path.exists(test.workpath('test1_wrap' + _obj))) + +test.fail_test(test.read('test2_wrap.c') != "test2.i\n") + +test.run(program = test.workpath('test3' + _exe), stdout = "test3.i\n") +test.fail_test(not os.path.exists(test.workpath('test3_wrap.cc'))) +test.fail_test(not os.path.exists(test.workpath('test3_wrap' + _obj))) + + + +swig = test.where_is('swig') + +if swig: + + test.write("wrapper.py", +"""import os +import string +import sys +open('%s', 'wb').write("wrapper.py\\n") +os.system(string.join(sys.argv[1:], " ")) +""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\')) + + test.write('SConstruct', """ +foo = Environment(SWIGFLAGS='-python', + CPPPATH='/usr/include/python1.5/', + SHCCFLAGS='', + SHOBJSUFFIX='.o', + SHLIBPREFIX='') +swig = foo.Dictionary('SWIG') +#bar = Environment(SWIG = r'%s wrapper.py ' + swig) +foo.SharedLibrary(target = 'example', source = ['example.c', 'example.i']) +#foo.SharedLibrary(target = 'foo', source = ['example.c']) +#foo.SharedLibrary(target = 'foo', source = ['example.i']) +#bar.SharedLibrary(target = 'bar', source = 'example.i') +""" % python) + + # Simple example.c and example.i stolen from the SWIG tutorial. + test.write("example.c", """\ +#include +double My_variable = 3.0; + +int fact(int n) { + if (n <= 1) return 1; + else return n*fact(n-1); +} + +int my_mod(int x, int y) { + return (x%y); +} + +char *get_time() +{ + return "Tue Aug 12 23:32:15 2003"; +} +""") + + test.write("example.i", """\ +%module example +%{ +/* Put header files here (optional) */ +%} + +extern double My_variable; +extern int fact(int n); +extern int my_mod(int x, int y); +extern char *get_time(); +""") + + test.run(arguments = '.') + + test.up_to_date(arguments = '.') + + test.run(program = python, stdin = """\ +import example +print example.fact(5) +print example.my_mod(7, 3) +print example.get_time() +""", stdout="""\ +120 +1 +Tue Aug 12 23:32:15 2003 +""") + + + +test.pass_test() diff --git a/test/import.py b/test/import.py index 99aa120..be3858a 100644 --- a/test/import.py +++ b/test/import.py @@ -99,6 +99,7 @@ tools = [ 'suncc', 'sunlink', 'Subversion', + 'swig', 'tar', 'tex', 'yacc', -- cgit v0.12