summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2002-04-22 19:21:26 (GMT)
committerSteven Knight <knight@baldmt.com>2002-04-22 19:21:26 (GMT)
commit74f98d3083cfbb859bf032dba37ed14bbb5219dd (patch)
tree331e7f6b4cb4d8ec98a4cce203ab22f4ba893f9b /src
parent9afa958bb1b299c47b3197589ab7b9b89686cc6b (diff)
downloadSCons-74f98d3083cfbb859bf032dba37ed14bbb5219dd.zip
SCons-74f98d3083cfbb859bf032dba37ed14bbb5219dd.tar.gz
SCons-74f98d3083cfbb859bf032dba37ed14bbb5219dd.tar.bz2
Several bug fixes from Charles Crain.
Diffstat (limited to 'src')
-rw-r--r--src/CHANGES.txt7
-rw-r--r--src/engine/SCons/Action.py40
-rw-r--r--src/engine/SCons/ActionTests.py55
-rw-r--r--src/engine/SCons/Builder.py50
-rw-r--r--src/engine/SCons/BuilderTests.py23
-rw-r--r--src/engine/SCons/Defaults.py100
-rw-r--r--src/engine/SCons/Node/FS.py6
-rw-r--r--src/engine/SCons/Node/FSTests.py2
-rw-r--r--src/engine/SCons/Script/__init__.py2
-rw-r--r--src/engine/SCons/Util.py23
-rw-r--r--src/engine/SCons/UtilTests.py18
11 files changed, 257 insertions, 69 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 29857f9..b7b07e4 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -58,6 +58,13 @@ RELEASE 0.07 -
- 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!
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 38b5413..1941e1a 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -198,6 +198,15 @@ def _do_create_action(act):
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:
@@ -314,11 +323,19 @@ class EnvDictProxy(UserDict.UserDict):
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."""
@@ -410,6 +427,21 @@ class CommandGeneratorAction(ActionBase):
"""
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):
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 648b77b..cda565d 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -178,14 +178,21 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
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
@@ -291,6 +298,39 @@ class ListActionTestCase(unittest.TestCase):
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()
@@ -300,7 +340,8 @@ if __name__ == "__main__":
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():
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index 2a1b7d8..05330e4 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -82,6 +82,17 @@ def Builder(**kw):
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)
@@ -129,6 +140,33 @@ def _adjust_suffix(suff):
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).
@@ -374,9 +412,15 @@ class MultiStepBuilder(BuilderBase):
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
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index e30079c..12178ed 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -86,6 +86,10 @@ class Environment:
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()
@@ -743,6 +747,25 @@ class BuilderTestCase(unittest.TestCase):
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():
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 8856db5..6acf4de 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -77,17 +77,28 @@ class SharedCmdGenerator:
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")
@@ -134,22 +145,33 @@ Object = SCons.Builder.Builder(name = 'Object',
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',
@@ -197,7 +219,7 @@ def win32LibGenerator(target, source, env, shared=1):
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:
@@ -226,32 +248,19 @@ def win32LibEmitter(target, source, env, shared=0):
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')
@@ -414,14 +423,10 @@ def make_win32_env_from_paths(include, lib, path):
'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',
@@ -449,7 +454,7 @@ def make_win32_env_from_paths(include, lib, path):
'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' : '',
@@ -464,6 +469,7 @@ def make_win32_env_from_paths(include, lib, path):
'LIBDIRSUFFIX' : '',
'LIBLINKPREFIX' : '',
'LIBLINKSUFFIX' : '$LIBSUFFIX',
+ 'LIBEMITTER' : win32LibEmitter,
'INCPREFIX' : '/I',
'INCSUFFIX' : '',
'WIN32DEFPREFIX' : '/def:',
@@ -491,8 +497,6 @@ def make_win32_env(version):
if os.name == 'posix':
- Library = PosixLibrary
-
arcom = '$AR $ARFLAGS $TARGET $SOURCES'
ranlib = 'ranlib'
if SCons.Util.WhereIs(ranlib):
@@ -557,7 +561,7 @@ if os.name == 'posix':
'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' : '',
@@ -578,8 +582,6 @@ if os.name == 'posix':
}
elif os.name == 'nt':
- Library = Win32Library
-
versions = None
try:
versions = get_devstudio_versions()
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index 1c506c9..861fbaf 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -204,7 +204,11 @@ class FS:
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)
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 6b35adb..0259deb 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -232,6 +232,8 @@ class FSTestCase(unittest.TestCase):
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)
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 09b85be..105c28f 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -124,6 +124,8 @@ class CleanTask(SCons.Taskmaster.Task):
os.unlink(t.path)
except OSError:
pass
+ else:
+ print "Removed " + t.path
except IndexError:
pass
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 0abbd1e..08edde7 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -139,6 +139,23 @@ class PathList(UserList.UserList):
# 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
@@ -272,8 +289,10 @@ def is_List(e):
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)
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index b424506..34dba99 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -268,25 +268,29 @@ class UtilTestCase(unittest.TestCase):
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 = '')
@@ -349,6 +353,14 @@ class UtilTestCase(unittest.TestCase):
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():