- ListBuilder now passes all targets to the action, not just the first.
+ - Fix so that -c now deletes generated yacc .h files.
+
+ - Builder actions and emitter functions can now be initialized, through
+ construction variables, to things other than strings.
+
+ - Make top-relative '#/dir' lookups work like '#dir'.
+
From Steven Knight:
- Fix so that -c -n does *not* remove the targets!
elif callable(act):
return FunctionAction(act)
elif SCons.Util.is_String(act):
+ var=SCons.Util.get_environment_var(act)
+ if var:
+ # This looks like a string that is purely an Environment
+ # variable reference, like "$FOO" or "${FOO}". We do
+ # something special here...we lazily evaluate the contents
+ # of that Environment variable, so a user could but something
+ # like a function or a CommandGenerator in that variable
+ # instead of a string.
+ return CommandGeneratorAction(LazyCmdGenerator(var))
listCmds = map(lambda x: CommandAction(string.split(x)),
string.split(act, '\n'))
if len(listCmds) == 1:
def __init__(self, env):
UserDict.UserDict.__init__(self, env)
- def subst(self, string):
- return SCons.Util.scons_subst(string, self.data, {}, _rm)
+ def subst(self, string, raw=0):
+ if raw:
+ regex_remove = None
+ else:
+ regex_remove = _rm
+ return SCons.Util.scons_subst(string, self.data, {}, regex_remove)
- def subst_list(self, string):
- return SCons.Util.scons_subst_list(string, self.data, {}, _rm)
+ def subst_list(self, string, raw=0):
+ if raw:
+ regex_remove = None
+ else:
+ regex_remove = _rm
+ return SCons.Util.scons_subst_list(string, self.data, {}, regex_remove)
class CommandAction(ActionBase):
"""Class for command-execution actions."""
"""
return apply(self.__generate(kw).get_contents, (), kw)
+class LazyCmdGenerator:
+ """This is a simple callable class that acts as a command generator.
+ It holds on to a key into an Environment dictionary, then waits
+ until execution time to see what type it is, then tries to
+ create an Action out of it."""
+ def __init__(self, var):
+ self.var = SCons.Util.to_String(var)
+
+ def __call__(self, env, **kw):
+ if env.has_key(self.var):
+ return env[self.var]
+ else:
+ # The variable reference substitutes to nothing.
+ return ''
+
class FunctionAction(ActionBase):
"""Class for Python function actions."""
def __init__(self, function):
def f(dummy, env, self=self):
self.dummy = dummy
- assert env.subst('$FOO') == 'foo baz\nbar ack', env.subst('$FOO')
- assert env.subst_list('$FOO') == [ [ 'foo', 'baz' ],
- [ 'bar', 'ack' ] ], env.subst_list('$FOO')
+ assert env.subst("$FOO $( bar $) baz") == 'foo baz\nbar ack bar baz', env.subst("$FOO $( bar $) baz")
+ assert env.subst("$FOO $( bar $) baz", raw=1) == 'foo baz\nbar ack $( bar $) baz', env.subst("$FOO $( bar $) baz", raw=1)
+ assert env.subst_list("$FOO $( bar $) baz") == [ [ 'foo', 'baz' ],
+ [ 'bar', 'ack', 'bar', 'baz' ] ], env.subst_list("$FOO $( bar $) baz")
+ assert env.subst_list("$FOO $( bar $) baz",
+ raw=1) == [ [ 'foo', 'baz' ],
+ [ 'bar', 'ack', '$(', 'bar', '$)', 'baz' ] ], env.subst_list("$FOO $( bar $) baz", raw=1)
return "$FOO"
def func_action(env, dummy, self=self):
- assert env.subst('$foo') == 'bar', env.subst('$foo')
- assert env.subst_list('$foo') == [ [ 'bar' ] ], env.subst_list('$foo')
- assert env.subst_list([ '$foo', 'bar' ]) == [[ 'bar', 'bar' ]], env.subst_list([ [ '$foo', 'bar' ] ])
+ assert env.subst('$foo $( bar $)') == 'bar bar', env.subst('$foo $( bar $)')
+ assert env.subst('$foo $( bar $)',
+ raw=1) == 'bar $( bar $)', env.subst('$foo $( bar $)', raw=1)
+ assert env.subst_list([ '$foo', '$(', 'bar', '$)' ]) == [[ 'bar', 'bar' ]], env.subst_list([ '$foo', '$(', 'bar', '$)' ])
+ assert env.subst_list([ '$foo', '$(', 'bar', '$)' ],
+ raw=1) == [[ 'bar', '$(', 'bar', '$)' ]], env.subst_list([ '$foo', '$(', 'bar', '$)' ], raw=1)
self.dummy=dummy
def f2(dummy, env, f=func_action):
return f
c = a.get_contents(target=[], source=[])
assert c == "xyz", c
+class LazyActionTestCase(unittest.TestCase):
+ def test_init(self):
+ """Test creation of a lazy-evaluation Action
+ """
+ # Environment variable references should create a special
+ # type of CommandGeneratorAction that lazily evaluates the
+ # variable.
+ a9 = SCons.Action.Action('$FOO')
+ assert isinstance(a9, SCons.Action.CommandGeneratorAction), a9
+ assert a9.generator.var == 'FOO', a9.generator.var
+
+ a10 = SCons.Action.Action('${FOO}')
+ assert isinstance(a9, SCons.Action.CommandGeneratorAction), a10
+ assert a10.generator.var == 'FOO', a10.generator.var
+
+ def test_execute(self):
+ """Test executing a lazy-evalueation Action
+ """
+ def f(s, env):
+ s.test=1
+ return 0
+ a = SCons.Action.Action('$BAR')
+ a.execute(s = self, env={'BAR':f})
+ assert self.test == 1, self.test
+
+ def test_get_contents(self):
+ """Test fetching the contents of a lazy-evaluation Action
+ """
+ a = SCons.Action.Action("${FOO}")
+ c = a.get_contents(target=[], source=[],
+ env={'FOO':[["This", "is", "$(", "a", "$)", "test"]]})
+ assert c == "This is test", c
+
if __name__ == "__main__":
suite = unittest.TestSuite()
for tclass in [CommandActionTestCase,
CommandGeneratorActionTestCase,
FunctionActionTestCase,
- ListActionTestCase]:
+ ListActionTestCase,
+ LazyActionTestCase]:
for func in ["test_init", "test_execute", "test_get_contents"]:
suite.addTest(tclass(func))
if not unittest.TextTestRunner().run(suite).wasSuccessful():
action_dict = kw['action']
kw['action'] = SCons.Action.CommandGenerator(DictCmdGenerator(action_dict))
kw['src_suffix'] = action_dict.keys()
+
+ if kw.has_key('emitter') and \
+ SCons.Util.is_String(kw['emitter']):
+ # This allows users to pass in an Environment
+ # variable reference (like "$FOO") as an emitter.
+ # We will look in that Environment variable for
+ # a callable to use as the actual emitter.
+ var = SCons.Util.get_environment_var(kw['emitter'])
+ if not var:
+ raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % kw['emitter']
+ kw['emitter'] = EmitterProxy(var)
if kw.has_key('src_builder'):
return apply(MultiStepBuilder, (), kw)
return '.' + suff
return suff
+class EmitterProxy:
+ """This is a callable class that can act as a
+ Builder emitter. It holds on to a string that
+ is a key into an Environment dictionary, and will
+ look there at actual build time to see if it holds
+ a callable. If so, we will call that as the actual
+ emitter."""
+ def __init__(self, var):
+ self.var = SCons.Util.to_String(var)
+
+ def __call__(self, target, source, env, **kw):
+ emitter = self.var
+
+ # Recursively substitue the variable.
+ # We can't use env.subst() because it deals only
+ # in strings. Maybe we should change that?
+ while SCons.Util.is_String(emitter) and \
+ env.has_key(emitter):
+ emitter = env[emitter]
+ if not callable(emitter):
+ return (target, source)
+ args = { 'target':target,
+ 'source':source,
+ 'env':env }
+ args.update(kw)
+ return apply(emitter, (), args)
+
class BuilderBase:
"""Base class for Builders, objects that create output
nodes (files) from input nodes (files).
dictArgs['env'] = env
tgt = apply(src_bld, (), dictArgs)
if not SCons.Util.is_List(tgt):
- final_sources.append(tgt)
- else:
- final_sources.extend(tgt)
+ tgt = [ tgt ]
+
+ # Only supply the builder with sources it is capable
+ # of building.
+ tgt = filter(lambda x,
+ suf=self.src_suffixes(env, kw):
+ os.path.splitext(SCons.Util.to_String(x))[1] in \
+ suf, tgt)
+ final_sources.extend(tgt)
else:
final_sources.append(snode)
dictKwArgs = kw
return {}
def autogenerate(self, dir=''):
return {}
+ def __getitem__(self, item):
+ return self.d[item]
+ def has_key(self, item):
+ return self.d.has_key(item)
env = Environment()
assert 'foo' in map(str, tgt.sources), map(str, tgt.sources)
assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
+ env2=Environment(FOO=emit)
+ builder2=SCons.Builder.Builder(name="builder2", action='foo',
+ emitter="$FOO")
+
+ tgt = builder2(env2, target='foo', source='bar')
+ assert str(tgt) == 'foo', str(tgt)
+ assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0])
+
+ tgt = builder2(env2, target='foo', source='bar', foo=1)
+ assert len(tgt) == 2, len(tgt)
+ assert 'foo' in map(str, tgt), map(str, tgt)
+ assert 'bar' in map(str, tgt), map(str, tgt)
+
+ tgt = builder2(env2, target='foo', source='bar', bar=1)
+ assert str(tgt) == 'foo', str(tgt)
+ assert len(tgt.sources) == 2, len(tgt.sources)
+ assert 'foo' in map(str, tgt.sources), map(str, tgt.sources)
+ assert 'bar' in map(str, tgt.sources), map(str, tgt.sources)
+
if __name__ == "__main__":
suite = unittest.makeSuite(BuilderTestCase, 'test_')
if not unittest.TextTestRunner().run(suite).wasSuccessful():
return self.action_shared
else:
return self.action_static
-
+
+def yaccEmitter(target, source, env, **kw):
+ # Yacc can be configured to emit a .h file as well
+ # as a .c file. Append that as a target.
+ if len(source) and os.path.splitext(SCons.Util.to_String(source[0]))[1] in \
+ [ '.y', '.yy']:
+ target.append(os.path.splitext(SCons.Util.to_String(target[0]))[0] + \
+ '.h')
+ return (target, source)
+
CFile = SCons.Builder.Builder(name = 'CFile',
action = { '.l' : '$LEXCOM',
'.y' : '$YACCCOM',
},
+ emitter = yaccEmitter,
suffix = '$CFILESUFFIX')
CXXFile = SCons.Builder.Builder(name = 'CXXFile',
action = { '.ll' : '$LEXCOM',
'.yy' : '$YACCCOM',
},
+ emitter = yaccEmitter,
suffix = '$CXXFILESUFFIX')
CXXAction = SCons.Action.Action("$CXXCOM")
src_suffix = static_obj.src_suffixes(),
src_builder = [CFile, CXXFile])
-def win32LinkGenerator(env, target, source, **kw):
- cmd = env.subst_list([ '$LINK', '$LINKFLAGS', '/OUT:' + str(target[0]) ])[0]
- cmd.extend(['$('] + env.subst_list('$_LIBDIRFLAGS')[0] + ['$)'])
- cmd.extend(env.subst_list('$_LIBFLAGS')[0])
- cmd.extend(map(lambda x: str(x), source))
+def win32TempFileMunge(env, cmd_list):
+ """Given a list of command line arguments, see if it is too
+ long to pass to the win32 command line interpreter. If so,
+ create a temp file, then pass "@tempfile" as the sole argument
+ to the supplied command (which is the first element of cmd_list).
+ Otherwise, just return [cmd_list]."""
+ cmd = env.subst_list(cmd_list)[0]
cmdlen = reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)
if cmdlen <= 2048:
- return [cmd]
+ return [cmd_list]
else:
import tempfile
- tmp = tempfile.mktemp()
- args = filter(lambda x: x != '$(' and x != '$)', cmd[1:])
- args = map(SCons.Util.quote_spaces, args)
+ # We do a normpath because mktemp() has what appears to be
+ # a bug in Win32 that will use a forward slash as a path
+ # delimiter. Win32's link mistakes that for a command line
+ # switch and barfs.
+ tmp = os.path.normpath(tempfile.mktemp())
+ args = map(SCons.Util.quote_spaces, cmd[1:])
open(tmp, 'w').write(string.join(args, " ") + "\n")
return [ [cmd[0], '@' + tmp],
['del', tmp] ]
+
+def win32LinkGenerator(env, target, source, **kw):
+ args = [ '$LINK', '$LINKFLAGS', '/OUT:%s' % target[0],
+ '$(', '$_LIBDIRFLAGS', '$)', '$_LIBFLAGS' ]
+ args.extend(map(SCons.Util.to_String, source))
+ return win32TempFileMunge(env, args)
kw = {
'name' : 'Program',
else:
# Just treat it as a generic source file.
listCmd.append(str(src))
- return [ listCmd ]
+ return win32TempFileMunge(env, listCmd)
def win32LibEmitter(target, source, env, shared=0):
if shared:
env.subst("$LIBSUFFIX")))
return (target, source)
-PosixLibrary = SCons.Builder.Builder(name = 'Library',
- generator = \
- SharedCmdGenerator(shared="$SHLINKCOM",
- static="$ARCOM"),
- prefix = \
- LibAffixGenerator(static='$LIBPREFIX',
- shared='$SHLIBPREFIX'),
- suffix = \
- LibAffixGenerator(static='$LIBSUFFIX',
- shared='$SHLIBSUFFIX'),
- src_suffix = '$OBJSUFFIX',
- src_builder = Object)
-
-Win32Library = SCons.Builder.Builder(name = 'Library',
- generator = \
- SharedCmdGenerator(shared=SCons.Action.CommandGeneratorAction(win32LibGenerator),
- static="$ARCOM"),
- emitter = win32LibEmitter,
- prefix = \
- LibAffixGenerator(static='$LIBPREFIX',
- shared='$SHLIBPREFIX'),
- suffix = \
- LibAffixGenerator(static='$LIBSUFFIX',
- shared='$SHLIBSUFFIX'),
- src_suffix = '$OBJSUFFIX',
- src_builder = Object)
+Library = SCons.Builder.Builder(name = 'Library',
+ generator = \
+ SharedCmdGenerator(shared="$SHLINKCOM",
+ static="$ARCOM"),
+ emitter="$LIBEMITTER",
+ prefix = \
+ LibAffixGenerator(static='$LIBPREFIX',
+ shared='$SHLIBPREFIX'),
+ suffix = \
+ LibAffixGenerator(static='$LIBSUFFIX',
+ shared='$SHLIBSUFFIX'),
+ src_suffix = '$OBJSUFFIX',
+ src_builder = Object)
LaTeXAction = SCons.Action.Action('$LATEXCOM')
'SHF77PPCOM' : '$SHF77 $SHF77FLAGS $CPPFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET',
'LINK' : 'link',
'LINKFLAGS' : '/nologo',
- # XXX - We'd like to do this as follows, but '$LINKCOM' in
- # a Builder above gets expanded too soon to stick a function
- # right in the environment like this. Revisit this when this
- # capability has been added (cf. bug report #537058).
- #'LINKCOM' : win32Link,
+ 'LINKCOM' : SCons.Action.CommandGenerator(win32LinkGenerator),
'SHLINK' : '$LINK',
'SHLINKFLAGS': '$LINKFLAGS /dll',
- 'SHLINKCOM' : '$SHLINK $SHLINKFLAGS /OUT:$TARGET $_LIBDIRFLAGS $_LIBFLAGS $SOURCES',
+ 'SHLINKCOM' : SCons.Action.CommandGenerator(win32LibGenerator),
'AR' : 'lib',
'ARFLAGS' : '/nologo',
'ARCOM' : '$AR $ARFLAGS /OUT:$TARGET $SOURCES',
'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES',
'PSPREFIX' : '',
'PSSUFFIX' : '.ps',
- 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Win32Library, Object,
+ 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object,
PDF, PostScript, Program],
'SCANNERS' : [CScan],
'OBJPREFIX' : '',
'LIBDIRSUFFIX' : '',
'LIBLINKPREFIX' : '',
'LIBLINKSUFFIX' : '$LIBSUFFIX',
+ 'LIBEMITTER' : win32LibEmitter,
'INCPREFIX' : '/I',
'INCSUFFIX' : '',
'WIN32DEFPREFIX' : '/def:',
if os.name == 'posix':
- Library = PosixLibrary
-
arcom = '$AR $ARFLAGS $TARGET $SOURCES'
ranlib = 'ranlib'
if SCons.Util.WhereIs(ranlib):
'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES',
'PSPREFIX' : '',
'PSSUFFIX' : '.ps',
- 'BUILDERS' : [Alias, CFile, CXXFile, DVI, PosixLibrary, Object,
+ 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object,
PDF, PostScript, Program],
'SCANNERS' : [CScan],
'OBJPREFIX' : '',
}
elif os.name == 'nt':
- Library = Win32Library
-
versions = None
try:
versions = get_devstudio_versions()
self.__setTopLevelDir()
if name[0] == '#':
directory = self.Top
- name = os.path.join('./', name[1:])
+ name = os.path.normpath(name[1:])
+ if name[0] == os.sep:
+ # Correct such that '#/foo' is equivalent
+ # to '#foo'.
+ name = name[1:]
elif not directory:
directory = self._cwd
return (os.path.normpath(name), directory)
Dir_test('.', './', sub_dir, sub)
Dir_test('./.', './', sub_dir, sub)
Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('#foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
+ Dir_test('#/foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
try:
f2 = fs.File(string.join(['f1', 'f2'], sep), directory = d1)
os.unlink(t.path)
except OSError:
pass
+ else:
+ print "Removed " + t.path
except IndexError:
pass
# suffix and basepath.
return self.__class__([ UserList.UserList.__getitem__(self, item), ])
+_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[^}]*})$')
+
+def get_environment_var(varstr):
+ """Given a string, first determine if it looks like a reference
+ to a single environment variable, like "$FOO" or "${FOO}".
+ If so, return that variable with no decorations ("FOO").
+ If not, return None."""
+ mo=_env_var.match(to_String(varstr))
+ if mo:
+ var = mo.group(1)
+ if var[0] == '{':
+ return var[1:-1]
+ else:
+ return var
+ else:
+ return None
+
def quote_spaces(arg):
if ' ' in arg or '\t' in arg:
return '"%s"' % arg
def to_String(s):
"""Better than str() because it will preserve a unicode
object without converting it to ASCII."""
- if is_String(s):
- return s
+ if hasattr(types, 'UnicodeType') and \
+ (type(s) is types.UnicodeType or \
+ (isinstance(s, UserString) and type(s.data) is types.UnicodeType)):
+ return unicode(s)
else:
return str(s)
import UserString
s1=UserString.UserString('blah')
- assert to_String(s1) is s1, s1
+ assert to_String(s1) == s1, s1
assert to_String(s1) == 'blah', s1
class Derived(UserString.UserString):
pass
s2 = Derived('foo')
- assert to_String(s2) is s2, s2
+ assert to_String(s2) == s2, s2
assert to_String(s2) == 'foo', s2
if hasattr(types, 'UnicodeType'):
s3=UserString.UserString(unicode('bar'))
- assert to_String(s3) is s3, s3
+ assert to_String(s3) == s3, s3
assert to_String(s3) == unicode('bar'), s3
+ assert type(to_String(s3)) is types.UnicodeType, \
+ type(to_String(s3))
except ImportError:
pass
if hasattr(types, 'UnicodeType'):
s4 = unicode('baz')
assert to_String(s4) == unicode('baz'), to_String(s4)
+ assert type(to_String(s4)) is types.UnicodeType, \
+ type(to_String(s4))
def test_WhereIs(self):
test = TestCmd.TestCmd(workdir = '')
wi = WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE')
assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi
+ def test_get_env_var(self):
+ """Testing get_environment_var()."""
+ assert get_environment_var("$FOO") == "FOO", get_environment_var("$FOO")
+ assert get_environment_var("${BAR}") == "BAR", get_environment_var("${BAR}")
+ assert get_environment_var("${BAR}FOO") == None, get_environment_var("${BAR}FOO")
+ assert get_environment_var("$BAR ") == None, get_environment_var("$BAR ")
+ assert get_environment_var("FOO$BAR") == None, get_environment_var("FOO$BAR")
+
if __name__ == "__main__":
suite = unittest.makeSuite(UtilTestCase, 'test_')
if not unittest.TextTestRunner().run(suite).wasSuccessful():
""" % string.replace(test.workpath('wrapper.out'), '\\', '\\\\'))
test.write('SConstruct', """
-foo = Environment()
+foo = Environment(YACCFLAGS='-d')
yacc = foo.Dictionary('YACC')
bar = Environment(YACC = r'%s wrapper.py ' + yacc)
foo.Program(target = 'foo', source = 'foo.y')
test.run(program = test.workpath('bar'), stdin = "b\n", stdout = "bar.y\n")
+ test.fail_test(not os.path.exists(test.workpath('foo.h')))
+
+ test.run(arguments = '-c .')
+
+ test.fail_test(os.path.exists(test.workpath('foo.h')))
+
test.pass_test()
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
+import os.path
import string
import sys
import TestSCons
test = TestSCons.TestSCons()
if sys.platform == 'win32':
+ lib_=''
+ _dll = '.dll'
linkflag = '/LIBPATH:' + test.workpath()
else:
+ lib_='lib'
+ _dll='.so'
linkflag = '-L' + test.workpath()
test.write('SConstruct', """
linkflags = linkflags + r' %s'
env = Environment(LINKFLAGS = '$LINKXXX', LINKXXX = linkflags)
env.Program(target = 'foo', source = 'foo.c')
+# Library(shared=1) uses $LINKFLAGS by default.
+env.Library(target = 'bar', source = 'foo.c', shared=1)
""" % (linkflag, linkflag))
test.write('foo.c', r"""
test.run(program = test.workpath('foo'), stdout = "foo.c\n")
+test.fail_test(not os.path.exists(lib_+'bar'+_dll))
+
test.pass_test()