From cb0829ad927a55c4d79125e6ced5e975544a05a6 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Wed, 23 Jan 2002 21:54:05 +0000 Subject: Strip $(-$) bracketed text from command lines. --- doc/man/scons.1 | 42 +++++++++++++++++++++++++++++++++++++++ src/CHANGES.txt | 4 ++++ src/engine/SCons/Action.py | 26 +++++++++++++++++++----- src/engine/SCons/ActionTests.py | 11 ++++++++-- src/engine/SCons/Builder.py | 5 +++++ src/engine/SCons/BuilderTests.py | 12 +++++++++-- src/engine/SCons/Node/FS.py | 4 +++- src/engine/SCons/Node/__init__.py | 5 +++++ src/engine/SCons/Util.py | 13 +++++++++--- src/engine/SCons/UtilTests.py | 34 ++++++++++++++++++++++++------- test/CPPPATH.py | 15 ++++++++++++++ test/LIBPATH.py | 12 +++++++++++ 12 files changed, 163 insertions(+), 20 deletions(-) diff --git a/doc/man/scons.1 b/doc/man/scons.1 index ca03f60..dfb96c7 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -1255,6 +1255,48 @@ ${TARGET.filebase} => file ${TARGET.suffix} => .x .EE +.LP +The special pseudo-variables +.R $( +and +.R $) +may be used to surround parts of a command line +that may change +.I without +causing a rebuild--that is, +which are not included in the signature +of target files built with this command. +All text between +.R $( +and +.R $) +will be removed from the command line +before it is added to file signatures, +and the +.R $( +and +.R $) +will be removed before the command is executed. +For example, the command line: + +.ES +echo Last build occurred $( $TODAY $). > $TARGET +.EE + +.LP +would execute the command: + +.ES +echo Last build occurred $TODAY. > $TARGET +.EE + +.LP +but the command signature added to any target files would be: + +.ES +echo Last build occurred . > $TARGET +.EE + .\" XXX document how to add user defined scanners. .SH EXAMPLES diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 462e06a..44946a0 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -38,6 +38,10 @@ RELEASE 0.04 - - The C Scanner now always returns a sorted list of dependencies so order changes don't cause unnecessary rebuilds. + - Strip $(-$) bracketed text from command lines. Use this to + surround $_INCDIRS and $_LIBDIRS so we don't rebuild in response + to changes to -I or -L options. + From Steve Leblanc: - Add var=value command-line arguments. diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 38a4c70..26e81a5 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -31,6 +31,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import os.path +import re import string import sys @@ -238,6 +239,9 @@ class ActionBase: return dict +_rm = re.compile(r'\$[()]') +_remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)') + class CommandAction(ActionBase): """Class for command-execution actions.""" def __init__(self, string): @@ -246,7 +250,7 @@ class CommandAction(ActionBase): def execute(self, **kw): import SCons.Util dict = apply(self.subst_dict, (), kw) - cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}) + cmd_list = SCons.Util.scons_subst_list(self.command, dict, {}, _rm) for cmd_line in cmd_list: if len(cmd_line): if print_actions: @@ -262,8 +266,8 @@ class CommandAction(ActionBase): return ret return 0 - def get_contents(self, **kw): - """Return the signature contents of this action's command line. + def _sig_dict(self, kw): + """Supply a dictionary for use in computing signatures. For signature purposes, it doesn't matter what targets or sources we use, so long as we use the same ones every time @@ -272,8 +276,20 @@ class CommandAction(ActionBase): """ kw['target'] = ['__t1__', '__t2__'] kw['source'] = ['__s1__', '__s2__'] - dict = apply(self.subst_dict, (), kw) - return SCons.Util.scons_subst(self.command, dict, {}) + return apply(self.subst_dict, (), kw) + + def get_raw_contents(self, **kw): + """Return the complete contents of this action's command line. + """ + return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}) + + def get_contents(self, **kw): + """Return the signature contents of this action's command line. + + This strips $(-$) and everything in between the string, + since those parts don't affect signatures. + """ + return SCons.Util.scons_subst(self.command, self._sig_dict(kw), {}, _remove) class FunctionAction(ActionBase): """Class for Python function actions.""" diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index a70fd99..30bf093 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -125,12 +125,19 @@ class CommandActionTestCase(unittest.TestCase): a.execute() assert t.executed == 1 + def test_get_raw_contents(self): + """Test fetching the contents of a command Action + """ + a = SCons.Action.CommandAction("| $( $foo | $bar $) |") + c = a.get_contents(foo = 'FFF', bar = 'BBB') + assert c == "| $( FFF | BBB $) |" + def test_get_contents(self): """Test fetching the contents of a command Action """ - a = SCons.Action.CommandAction("| $foo | $bar |") + a = SCons.Action.CommandAction("| $foo $( | $) $bar |") c = a.get_contents(foo = 'FFF', bar = 'BBB') - assert c == "| FFF | BBB |" + assert c == "| FFF BBB |" class FunctionActionTestCase(unittest.TestCase): diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index db1e701..6fffeee 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -145,6 +145,11 @@ class BuilderBase: """ return apply(self.action.execute, (), kw) + def get_raw_contents(self, **kw): + """Fetch the "contents" of the builder's action. + """ + return apply(self.action.get_raw_contents, (), kw) + def get_contents(self, **kw): """Fetch the "contents" of the builder's action (for signature calculation). diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index e383ee1..65aa934 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -349,8 +349,11 @@ class BuilderTestCase(unittest.TestCase): 'INCPREFIX' : '-I', 'INCSUFFIX' : ''} + contents = apply(b4.get_raw_contents, (), kw) + assert contents == "-ll1 -ll2 $( -LlibX $) $( -Ic -Ip $)", contents + contents = apply(b4.get_contents, (), kw) - assert contents == "-ll1 -ll2 -LlibX -Ic -Ip", contents + assert contents == "-ll1 -ll2", "'%s'" % contents # SCons.Node.FS has been imported by our import of # SCons.Node.Builder. It's kind of bogus that we don't @@ -358,8 +361,13 @@ class BuilderTestCase(unittest.TestCase): # maybe a little cleaner than tying these tests directly # to the other module via a direct import. kw['dir'] = SCons.Node.FS.default_fs.Dir('d') + + contents = apply(b4.get_raw_contents, (), kw) + expect = os.path.normpath("-ll1 -ll2 $( -Ld/libX $) $( -Id/c -Id/p $)") + assert contents == expect, contents + " != " + expect + contents = apply(b4.get_contents, (), kw) - expect = os.path.normpath("-ll1 -ll2 -Ld/libX -Id/c -Id/p") + expect = os.path.normpath("-ll1 -ll2") assert contents == expect, contents + " != " + expect def test_node_factory(self): diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 66b7314..96ce148 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -162,6 +162,7 @@ class FS: except KeyError: dir_temp = Dir(path_name, directory) directory.entries[path_norm] = dir_temp + directory.add_wkid(dir_temp) directory = dir_temp file_name = _my_normcase(path_comp[-1]) try: @@ -169,6 +170,7 @@ class FS: except KeyError: ret = fsclass(path_comp[-1], directory) directory.entries[file_name] = ret + directory.add_wkid(ret) return ret def __transformPath(self, name, directory): @@ -431,7 +433,7 @@ class Dir(Entry): if s and (not state or s > state): state = s import SCons.Node - if state == SCons.Node.up_to_date: + if state == 0 or state == SCons.Node.up_to_date: return 1 else: return 0 diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index b11027f..f2d379f 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -190,6 +190,11 @@ class Node: if parent not in self.parents: self.parents.append(parent) + 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(self): #XXX Need to remove duplicates from this return self.sources \ diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index a207ffc..16d2c76 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -170,7 +170,7 @@ class PathList(UserList.UserList): _cv = re.compile(r'\$([_a-zA-Z]\w*|{[^}]*})') _space_sep = re.compile(r'[\t ]+(?![^{]*})') -def scons_subst_list(strSubst, locals, globals): +def scons_subst_list(strSubst, locals, globals, remove=None): """ This function is similar to scons_subst(), but with one important difference. Instead of returning a single @@ -214,10 +214,12 @@ def scons_subst_list(strSubst, locals, globals): strSubst, n = _cv.subn(repl, strSubst) # Now parse the whole list into tokens. listLines = string.split(strSubst, '\n') + if remove: + listLines = map(lambda x,re=remove: re.sub('', x), listLines) return map(lambda x: filter(lambda y: y, string.split(x, '\0')), listLines) -def scons_subst(strSubst, locals, globals): +def scons_subst(strSubst, locals, globals, remove=None): """Recursively interpolates dictionary variables into the specified string, returning the expanded result. Variables are specified by a $ prefix in the string and @@ -227,7 +229,7 @@ def scons_subst(strSubst, locals, globals): surrounded by curly braces to separate the name from trailing characters. """ - cmd_list = scons_subst_list(strSubst, locals, globals) + cmd_list = scons_subst_list(strSubst, locals, globals, remove) return string.join(map(string.join, cmd_list), '\n') def find_files(filenames, paths, @@ -351,6 +353,11 @@ class DirVarInterp(VarInterpolator): self.dictInstCache[(dir, fs)] = ret return ret + def generate(self, dict): + VarInterpolator.generate(self, dict) + if dict[self.dest]: + dict[self.dest] = ['$('] + dict[self.dest] + ['$)'] + AUTO_GEN_VARS = ( VarInterpolator('_LIBFLAGS', 'LIBS', 'LIBLINKPREFIX', diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index e9e763c..ed72ebb 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -25,6 +25,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import os.path +import re import string import sys import unittest @@ -127,6 +128,21 @@ class UtilTestCase(unittest.TestCase): newcom = scons_subst("test $xxx", loc, {}) assert newcom == cvt("test"), newcom + newcom = scons_subst("test $($xxx$)", loc, {}) + assert newcom == cvt("test $($)"), newcom + + newcom = scons_subst("test $( $xxx $)", loc, {}) + assert newcom == cvt("test $( $)"), newcom + + newcom = scons_subst("test $($xxx$)", loc, {}, re.compile('\$[()]')) + assert newcom == cvt("test"), newcom + + newcom = scons_subst("test $( $xxx $)", loc, {}, re.compile('\$[()]')) + assert newcom == cvt("test"), newcom + + newcom = scons_subst("test aXbXcXd", loc, {}, re.compile('X')) + assert newcom == cvt("test abcd"), newcom + def test_subst_list(self): """Testing the scons_subst_list() method...""" loc = {} @@ -196,18 +212,22 @@ class UtilTestCase(unittest.TestCase): 'INCSUFFIX' : 'bar', 'FOO' : 'baz' } autogenerate(dict, dir = SCons.Node.FS.default_fs.Dir('/xx')) - assert len(dict['_INCFLAGS']) == 5, dict['_INCFLAGS'] - assert dict['_INCFLAGS'][0] == os.path.normpath('foo/xx/foobar'), \ + assert len(dict['_INCFLAGS']) == 7, dict['_INCFLAGS'] + assert dict['_INCFLAGS'][0] == '$(', \ dict['_INCFLAGS'][0] - assert dict['_INCFLAGS'][1] == os.path.normpath('foo/xx/barbar'), \ + assert dict['_INCFLAGS'][1] == os.path.normpath('foo/xx/foobar'), \ dict['_INCFLAGS'][1] - assert dict['_INCFLAGS'][2] == os.path.normpath('foo/xx/bazbar'), \ + assert dict['_INCFLAGS'][2] == os.path.normpath('foo/xx/barbar'), \ dict['_INCFLAGS'][2] - assert dict['_INCFLAGS'][3] == os.path.normpath('foo/xx/baz/barbar'), \ + assert dict['_INCFLAGS'][3] == os.path.normpath('foo/xx/bazbar'), \ dict['_INCFLAGS'][3] - - assert dict['_INCFLAGS'][4] == os.path.normpath('fooblatbar'), \ + assert dict['_INCFLAGS'][4] == os.path.normpath('foo/xx/baz/barbar'), \ dict['_INCFLAGS'][4] + + assert dict['_INCFLAGS'][5] == os.path.normpath('fooblatbar'), \ + dict['_INCFLAGS'][5] + assert dict['_INCFLAGS'][6] == '$)', \ + dict['_INCFLAGS'][6] def test_render_tree(self): class Node: diff --git a/test/CPPPATH.py b/test/CPPPATH.py index af7bdd8..30439ad 100644 --- a/test/CPPPATH.py +++ b/test/CPPPATH.py @@ -161,4 +161,19 @@ test.fail_test(os.path.exists(test.workpath('variant', 'prog.c'))) test.up_to_date(arguments = args) +# Change CPPPATH and make sure we don't rebuild because of it. +test.write('SConstruct', """ +env = Environment(CPPPATH = ['include']) +obj = env.Object(target='prog', source='subdir/prog.c') +env.Program(target='prog', source=obj) +SConscript('subdir/SConscript', "env") + +BuildDir('variant', 'subdir', 0) +include = Dir('include') +env = Environment(CPPPATH=[include]) +SConscript('variant/SConscript', "env") +""") + +test.up_to_date(arguments = args) + test.pass_test() diff --git a/test/LIBPATH.py b/test/LIBPATH.py index 7d5a7cc..a373425 100644 --- a/test/LIBPATH.py +++ b/test/LIBPATH.py @@ -60,4 +60,16 @@ test.run(arguments = '.') test.run(program = test.workpath('prog'), stdout = "f1.c\nprog.c\n") +test.up_to_date(arguments = '.') + +# Change LIBPATH and make sure we don't rebuild because of it. +test.write('SConstruct', """ +env = Environment(LIBS = [ 'foo1' ], + LIBPATH = [ './libs', './lib2' ]) +env.Program(target = 'prog', source = 'prog.c') +env.Library(target = './libs/foo1', source = 'f1.c') +""") + +test.up_to_date(arguments = '.', stderr = None) + test.pass_test() -- cgit v0.12