diff options
-rw-r--r-- | QMTest/TestSCons.py | 17 | ||||
-rw-r--r-- | QMTest/TestSConsign.py | 24 | ||||
-rw-r--r-- | doc/user/add-method.in | 8 | ||||
-rw-r--r-- | doc/user/add-method.xml | 6 | ||||
-rw-r--r-- | doc/user/builders-writing.in | 138 | ||||
-rw-r--r-- | doc/user/builders-writing.xml | 123 | ||||
-rw-r--r-- | doc/user/main.in | 6 | ||||
-rw-r--r-- | doc/user/main.xml | 6 | ||||
-rw-r--r-- | src/CHANGES.txt | 30 | ||||
-rw-r--r-- | src/engine/SCons/Action.py | 13 | ||||
-rw-r--r-- | src/engine/SCons/ActionTests.py | 30 | ||||
-rw-r--r-- | src/engine/SCons/Node/Alias.py | 3 | ||||
-rw-r--r-- | src/engine/SCons/Node/FS.py | 3 | ||||
-rw-r--r-- | src/engine/SCons/Node/Python.py | 5 | ||||
-rw-r--r-- | src/engine/SCons/Taskmaster.py | 15 | ||||
-rw-r--r-- | src/engine/SCons/Tool/dmd.py | 2 | ||||
-rw-r--r-- | src/engine/SCons/Tool/g77.py | 4 | ||||
-rw-r--r-- | src/engine/SCons/Tool/intelc.py | 2 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msvc.py | 35 | ||||
-rw-r--r-- | src/script/sconsign.py | 4 | ||||
-rw-r--r-- | test/Actions/function.py | 56 | ||||
-rw-r--r-- | test/Configure/build-fail.py | 90 | ||||
-rw-r--r-- | test/Configure/implicit-cache.py | 103 | ||||
-rw-r--r-- | test/KeyboardInterrupt.py | 46 | ||||
-rw-r--r-- | test/Value.py | 18 | ||||
-rw-r--r-- | test/explain/basic.py | 10 |
26 files changed, 697 insertions, 100 deletions
diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py index 1277af5..cfcbfb1 100644 --- a/QMTest/TestSCons.py +++ b/QMTest/TestSCons.py @@ -338,9 +338,8 @@ class TestSCons(TestCommon): if options: arguments = options + " " + arguments kw['arguments'] = arguments - kw['stdout'] = self.wrap_stdout(read_str = read_str, build_str = s) - kw['stdout'] = string.replace(kw['stdout'],'\n','\\n') - kw['stdout'] = string.replace(kw['stdout'],'.','\\.') + stdout = self.wrap_stdout(read_str = read_str, build_str = s) + kw['stdout'] = re.escape(stdout) kw['match'] = self.match_re_dotall apply(self.run, [], kw) @@ -350,14 +349,14 @@ class TestSCons(TestCommon): 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 + for arg in string.split(arguments): + s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg) if options: arguments = options + " " + arguments + s = '('+s+'[^\n]*\n)*' kw['arguments'] = arguments - kw['stdout'] = self.wrap_stdout(build_str="("+s+"[^\n]*\n)*") - kw['stdout'] = string.replace(kw['stdout'],'\n','\\n') - kw['stdout'] = string.replace(kw['stdout'],'.','\\.') + stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE')) + kw['stdout'] = string.replace(stdout, 'ARGUMENTSGOHERE', s) kw['match'] = self.match_re_dotall apply(self.run, [], kw) @@ -833,7 +832,7 @@ print "self._msvs_versions =", str(env['MSVS']['VERSIONS']) def __init__(self, p): self.pos = p - def matchPart(log, logfile, lastEnd): + def matchPart(log, logfile, lastEnd, NoMatch=NoMatch): m = re.match(log, logfile[lastEnd:]) if not m: raise NoMatch, lastEnd diff --git a/QMTest/TestSConsign.py b/QMTest/TestSConsign.py index d144040..fd8ef30 100644 --- a/QMTest/TestSConsign.py +++ b/QMTest/TestSConsign.py @@ -54,13 +54,23 @@ class TestSConsign(TestSCons): 'interpreter' : python, # imported from TestSCons } - if os.path.exists(self.script_path('sconsign.py')): - sconsign = 'sconsign.py' - elif os.path.exists(self.script_path('sconsign')): - sconsign = 'sconsign' - else: - print "Can find neither 'sconsign.py' nor 'sconsign' scripts." - self.no_result() + if not kw.has_key('program'): + kw['program'] = os.environ.get('SCONS') + if not kw['program']: + if os.path.exists('scons'): + kw['program'] = 'scons' + else: + kw['program'] = 'scons.py' + + sconsign = os.environ.get('SCONSIGN') + if not sconsign: + if os.path.exists(self.script_path('sconsign.py')): + sconsign = 'sconsign.py' + elif os.path.exists(self.script_path('sconsign')): + sconsign = 'sconsign' + else: + print "Can find neither 'sconsign.py' nor 'sconsign' scripts." + self.no_result() self.set_sconsign(sconsign) def script_path(self, script): diff --git a/doc/user/add-method.in b/doc/user/add-method.in index 853b9a8..a0a6039 100644 --- a/doc/user/add-method.in +++ b/doc/user/add-method.in @@ -72,7 +72,7 @@ <scons_example name="ex2"> <file name="SConstruct" printme="1"> - import sys; + import sys def BuildTestProg(env, testfile, resourcefile, testdir="tests"): """Build the test program; prepends "test_" to src and target, and puts target into testdir.""" @@ -103,3 +103,9 @@ <scons_output_command>scons -Q</scons_output_command> </scons_output> + <para> + Using AddMethod is better than just adding an instance method to an + Environment because it gets called as a proper method, and AddMethod + provides for copying the method to any copies of the Environment + instance. + </para> diff --git a/doc/user/add-method.xml b/doc/user/add-method.xml index 22f5f1a..7e84b80 100644 --- a/doc/user/add-method.xml +++ b/doc/user/add-method.xml @@ -98,3 +98,9 @@ cc -o tests/test_stuff test_stuff.o </screen> + <para> + Using AddMethod is better than just adding an instance method to an + Environment because it gets called as a proper method, and AddMethod + provides for copying the method to any copies of the Environment + instance. + </para> diff --git a/doc/user/builders-writing.in b/doc/user/builders-writing.in index 2285ac8..67a95f9 100644 --- a/doc/user/builders-writing.in +++ b/doc/user/builders-writing.in @@ -761,6 +761,144 @@ This functionality could be invoked as in the following example: </section> + <section> + <title>Where To Put Your Custom Builders and Tools</title> + + <para> + + The <filename>site_scons</filename> directory gives you a place to + put Python modules you can import into your SConscripts + (site_scons), add-on tools that can integrate into &SCons; + (site_scons/site_tools), and a site_scons/site_init.py file that + gets read before any &SConstruct; or &SConscript;, allowing you to + change &SCons;'s default behavior. + + </para> + + <para> + + If you get a tool from somewhere (the &SCons; wiki or a third party, + for instance) and you'd like to use it in your project, the + <filename>site_scons</filename> dir is the simplest place to put it. + Tools come in two flavors; either a Python function that operates on + an &Environment; or a Python file containing two functions, exists() + and generate(). + + </para> + + <para> + + A single-function Tool can just be included in your + <filename>site_scons/site_init.py</filename> file where it will be + parsed and made available for use. For instance, you could have a + <filename>site_scons/site_init.py</filename> file like this: + + </para> + + <scons_example name="site1"> + <file name="site_scons/site_init.py" printme=1> + def TOOL_ADD_HEADER(env): + """A Tool to add a header from $HEADER to the source file""" + add_header = Builder(action=['echo "$HEADER" > $TARGET', + 'cat $SOURCE >> $TARGET']) + env.Append(BUILDERS = {'AddHeader' : add_header}) + env['HEADER'] = '' # set default value + </file> + <file name="SConstruct"> + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + </file> + <file name="src"> + hi there + </file> + </scons_example> + + <para> + + and a &SConstruct; like this: + + </para> + + <sconstruct> + # Use TOOL_ADD_HEADER from site_scons/site_init.py + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + </sconstruct> + + <para> + + The <function>TOOL_ADD_HEADER</function> tool method will be + called to add the <function>AddHeader</function> tool to the + environment. + + </para> + + <!-- + <scons_output example="site1" os="posix"> + <scons_output_command>scons -Q</scons_output_command> + </scons_output> + --> + + <para> + Similarly, a more full-fledged tool with + <function>exists()</function> and <function>generate()</function> + methods can be installed in + <filename>site_scons/site_tools/toolname.py</filename>. Since + <filename>site_scons/site_tools</filename> is automatically added + to the head of the tool search path, any tool found there will be + available to all environments. Furthermore, a tool found there + will override a built-in tool of the same name, so if you need to + change the behavior of a built-in tool, site_scons gives you the + hook you need. + </para> + + <para> + Many people have a library of utility Python functions they'd like + to include in &SConscript;s; just put that module in + <filename>site_scons/my_utils.py</filename> or any valid Python module name of your + choice. For instance you can do something like this in + <filename>site_scons/my_utils.py</filename> to add a build_id method: + </para> + + <scons_example name="site2"> + <file name="site_scons/my_utils.py" printme=1> + def build_id(): + """Return a build ID (stub version)""" + return "100" + </file> + <file name="SConscript"> + import my_utils + print "build_id=" + my_utils.build_id() + </file> + </scons_example> + + <para> + + And then in your &SConscript; or any sub-&SConscript; anywhere in + your build, you can import <filename>my_utils</filename> and use it: + + </para> + + <sconstruct> + import my_utils + print "build_id=" + my_utils.build_id() + </sconstruct> + + <para> + + If you have a machine-wide site dir you'd like to use instead of + <filename>./site_scons</filename>, use the + <literal>--site-dir</literal> option to point to your dir. + <filename>site_init.py</filename> and + <filename>site_tools</filename> will be located under that dir. + To avoid using a <filename>site_scons</filename> dir at all, even + if it exists, use the <literal>--no-site-dir</literal> option. + + </para> + + </section> + + <!-- <section> diff --git a/doc/user/builders-writing.xml b/doc/user/builders-writing.xml index 8ab4fca..df3affa 100644 --- a/doc/user/builders-writing.xml +++ b/doc/user/builders-writing.xml @@ -659,6 +659,129 @@ This functionality could be invoked as in the following example: </section> + <section> + <title>Where To Put Your Custom Builders and Tools</title> + + <para> + + The <filename>site_scons</filename> directory gives you a place to + put Python modules you can import into your SConscripts + (site_scons), add-on tools that can integrate into &SCons; + (site_scons/site_tools), and a site_scons/site_init.py file that + gets read before any &SConstruct; or &SConscript;, allowing you to + change &SCons;'s default behavior. + + </para> + + <para> + + If you get a tool from somewhere (the &SCons; wiki or a third party, + for instance) and you'd like to use it in your project, the + <filename>site_scons</filename> dir is the simplest place to put it. + Tools come in two flavors; either a Python function that operates on + an &Environment; or a Python file containing two functions, exists() + and generate(). + + </para> + + <para> + + A single-function Tool can just be included in your + <filename>site_scons/site_init.py</filename> file where it will be + parsed and made available for use. For instance, you could have a + <filename>site_scons/site_init.py</filename> file like this: + + </para> + + <programlisting> + def TOOL_ADD_HEADER(env): + """A Tool to add a header from $HEADER to the source file""" + add_header = Builder(action=['echo "$HEADER" > $TARGET', + 'cat $SOURCE >> $TARGET']) + env.Append(BUILDERS = {'AddHeader' : add_header}) + env['HEADER'] = '' # set default value + </programlisting> + + <para> + + and a &SConstruct; like this: + + </para> + + <programlisting> + # Use TOOL_ADD_HEADER from site_scons/site_init.py + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + </programlisting> + + <para> + + The <function>TOOL_ADD_HEADER</function> tool method will be + called to add the <function>AddHeader</function> tool to the + environment. + + </para> + + <!-- + <scons_output example="site1" os="posix"> + <scons_output_command>scons -Q</scons_output_command> + </scons_output> + --> + + <para> + Similarly, a more full-fledged tool with + <function>exists()</function> and <function>generate()</function> + methods can be installed in + <filename>site_scons/site_tools/toolname.py</filename>. Since + <filename>site_scons/site_tools</filename> is automatically added + to the head of the tool search path, any tool found there will be + available to all environments. Furthermore, a tool found there + will override a built-in tool of the same name, so if you need to + change the behavior of a built-in tool, site_scons gives you the + hook you need. + </para> + + <para> + Many people have a library of utility Python functions they'd like + to include in &SConscript;s; just put that module in + <filename>site_scons/my_utils.py</filename> or any valid Python module name of your + choice. For instance you can do something like this in + <filename>site_scons/my_utils.py</filename> to add a build_id method: + </para> + + <programlisting> + def build_id(): + """Return a build ID (stub version)""" + return "100" + </programlisting> + + <para> + + And then in your &SConscript; or any sub-&SConscript; anywhere in + your build, you can import <filename>my_utils</filename> and use it: + + </para> + + <programlisting> + import my_utils + print "build_id=" + my_utils.build_id() + </programlisting> + + <para> + + If you have a machine-wide site dir you'd like to use instead of + <filename>./site_scons</filename>, use the + <literal>--site-dir</literal> option to point to your dir. + <filename>site_init.py</filename> and + <filename>site_tools</filename> will be located under that dir. + To avoid using a <filename>site_scons</filename> dir at all, even + if it exists, use the <literal>--no-site-dir</literal> option. + + </para> + + </section> + + <!-- <section> diff --git a/doc/user/main.in b/doc/user/main.in index 8981b14..0dbadfc 100644 --- a/doc/user/main.in +++ b/doc/user/main.in @@ -135,16 +135,10 @@ XXX CheckTypeSize() - XXX Glob() - XXX Progress() XXX - - diskcheck= - XXX site_scons - XXX - - site-dir - XXX - - no-site-dir - XXX - - warn= XXX ARGLIST diff --git a/doc/user/main.xml b/doc/user/main.xml index 8981b14..0dbadfc 100644 --- a/doc/user/main.xml +++ b/doc/user/main.xml @@ -135,16 +135,10 @@ XXX CheckTypeSize() - XXX Glob() - XXX Progress() XXX - - diskcheck= - XXX site_scons - XXX - - site-dir - XXX - - no-site-dir - XXX - - warn= XXX ARGLIST diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 57cafa5..5a780b5 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -8,6 +8,36 @@ +RELEASE 0.98.4 - XXX + + From Benoit Belley: + + - Fix calculation of signatures for Python function actions with + closures in Python versions before 2.5. + + From David Cournapeau: + + - Fix the initialization of $SHF77FLAGS so it includes $F77FLAGS. + + From Jonas Olsson: + + - Fix a syntax error in the Intel C compiler support on Windows. + + From Steven Knight: + + - Change how we represent Python Value Nodes when printing and when + stored in .sconsign files (to avoid blowing out memory by storing + huge strings in .sconsign files after multiple runs using Configure + contexts cause the Value strings to be re-escaped each time). + + - Fix a regression in not executing configuration checks after failure + of any configuration check that used the same compiler or other tool. + + - Handle multiple destinations in Visual Studio 8 settings for the + analogues to the INCLUDE, LIBRARY and PATH variables. + + + RELEASE 0.98.3 - Tue, 29 Apr 2008 22:40:12 -0700 From Greg Noel: diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index d2211b7..1bc9baac 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -277,7 +277,10 @@ def _function_contents(func): closure = [] #xxx = [_object_contents(x.cell_contents) for x in closure] - xxx = map(lambda x: _object_contents(x.cell_contents), closure) + try: + xxx = map(lambda x: _object_contents(x.cell_contents), closure) + except AttributeError: + xxx = [] contents.append(',(' + string.join(xxx, ',') + ')') return string.join(contents, '') @@ -809,7 +812,13 @@ class FunctionAction(_ActionAction): return c def array(a): def quote(s): - return '"' + str(s) + '"' + try: + str_for_display = s.str_for_display + except AttributeError: + s = repr(s) + else: + s = str_for_display() + return s return '[' + string.join(map(quote, a), ", ") + ']' try: strfunc = self.execfunction.strfunction diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index a503981..2fb3b06 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -172,6 +172,8 @@ class Environment: class DummyNode: def __init__(self, name): self.name = name + def str_for_display(self): + return '"' + self.name + '"' def __str__(self): return self.name def rfile(self): @@ -432,10 +434,10 @@ class _ActionActionTestCase(unittest.TestCase): result = a("out", "in", env) assert result.status == 7, result s = sio.getvalue() - assert s == 'execfunc(["out"], ["in"])\n', s + assert s == "execfunc(['out'], ['in'])\n", s a.chdir = 'xyz' - expect = 'os.chdir(%s)\nexecfunc(["out"], ["in"])\nos.chdir(%s)\n' + expect = "os.chdir(%s)\nexecfunc(['out'], ['in'])\nos.chdir(%s)\n" sio = StringIO.StringIO() sys.stdout = sio @@ -458,7 +460,7 @@ class _ActionActionTestCase(unittest.TestCase): result = b("out", "in", env) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\n', s + assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\n", s SCons.Action.execute_actions = 0 @@ -467,14 +469,14 @@ class _ActionActionTestCase(unittest.TestCase): result = a("out", "in", env) assert result == 0, result s = sio.getvalue() - assert s == 'execfunc(["out"], ["in"])\n', s + assert s == "execfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = b("out", "in", env) assert result == 0, result s = sio.getvalue() - assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\nlastfunc(["out"], ["in"])\n', s + assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\nlastfunc(['out'], ['in'])\n", s SCons.Action.print_actions_presub = 1 SCons.Action.execute_actions = 1 @@ -484,35 +486,35 @@ class _ActionActionTestCase(unittest.TestCase): result = a("out", "in", env) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, presub=0) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'execfunc(["out"], ["in"])\n', s + assert s == "execfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = a("out", "in", env, presub=1) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = b(["out"], "in", env, presub=1) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'Building out with action:\n firstfunc(target, source, env)\nfirstfunc(["out"], ["in"])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s + assert s == "Building out with action:\n firstfunc(target, source, env)\nfirstfunc(['out'], ['in'])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = b(["out", "list"], "in", env, presub=1) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(["out", "list"], ["in"])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(["out", "list"], ["in"])\n', s + assert s == "Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(['out', 'list'], ['in'])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(['out', 'list'], ['in'])\n", s a2 = SCons.Action.Action(execfunc) @@ -521,14 +523,14 @@ class _ActionActionTestCase(unittest.TestCase): result = a2("out", "in", env) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio result = a2("out", "in", env, presub=0) assert result.status == 7, result.status s = sio.getvalue() - assert s == 'execfunc(["out"], ["in"])\n', s + assert s == "execfunc(['out'], ['in'])\n", s SCons.Action.execute_actions = 0 @@ -537,7 +539,7 @@ class _ActionActionTestCase(unittest.TestCase): result = a2("out", "in", env, presub=0) assert result == 0, result s = sio.getvalue() - assert s == 'execfunc(["out"], ["in"])\n', s + assert s == "execfunc(['out'], ['in'])\n", s sio = StringIO.StringIO() sys.stdout = sio @@ -568,7 +570,7 @@ class _ActionActionTestCase(unittest.TestCase): result.append(s) env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line a("output", "input", env) - assert result == ['execfunc(["output"], ["input"])'], result + assert result == ["execfunc(['output'], ['input'])"], result finally: diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py index bb23d3f..93558f9 100644 --- a/src/engine/SCons/Node/Alias.py +++ b/src/engine/SCons/Node/Alias.py @@ -74,6 +74,9 @@ class Alias(SCons.Node.Node): SCons.Node.Node.__init__(self) self.name = name + def str_for_display(self): + return '"' + self.__str__() + '"' + def __str__(self): return self.name diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index bd8b564..b154e34 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -544,6 +544,9 @@ class Base(SCons.Node.Node): self.cwd = None # will hold the SConscript directory for target nodes self.duplicate = directory.duplicate + def str_for_display(self): + return '"' + self.__str__() + '"' + def must_be_same(self, klass): """ This node, which already existed, is being looked up as the diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py index 7cdea14..741d7e9 100644 --- a/src/engine/SCons/Node/Python.py +++ b/src/engine/SCons/Node/Python.py @@ -56,9 +56,12 @@ class Value(SCons.Node.Node): if not built_value is None: self.built_value = built_value - def __str__(self): + def str_for_display(self): return repr(self.value) + def __str__(self): + return str(self.value) + def make_ready(self): self.get_csig() diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py index 6f93ca7..27dd1f0 100644 --- a/src/engine/SCons/Taskmaster.py +++ b/src/engine/SCons/Taskmaster.py @@ -529,7 +529,7 @@ class Taskmaster: while self.candidates: candidates = self.candidates self.candidates = [] - self.will_not_build(candidates) + self.will_not_build(candidates, lambda n: n.state < NODE_UP_TO_DATE) return None def _find_next_ready_node(self): @@ -634,6 +634,9 @@ class Taskmaster: # take a stab at evaluating them (or their children). children_not_visited.reverse() self.candidates.extend(self.order(children_not_visited)) + #if T and children_not_visited: + # T.write('Taskmaster: adding to candidates: %s\n' % map(str, children_not_visited)) + # T.write('Taskmaster: candidates now: %s\n' % map(str, self.candidates)) # Skip this node if any of its children have failed. # @@ -731,7 +734,7 @@ class Taskmaster: return task - def will_not_build(self, nodes): + def will_not_build(self, nodes, mark_fail=lambda n: n.state != NODE_FAILED): """ Perform clean-up about nodes that will never be built. """ @@ -742,8 +745,8 @@ class Taskmaster: for node in nodes: # Set failure state on all of the parents that were dependent # on this failed build. - if node.state != NODE_FAILED: - node.state = NODE_FAILED + if mark_fail(node): + node.set_state(NODE_FAILED) parents = node.waiting_parents to_visit = to_visit | parents pending_children = pending_children - parents @@ -759,8 +762,8 @@ class Taskmaster: to_visit.remove(node) else: break - if node.state != NODE_FAILED: - node.state = NODE_FAILED + if mark_fail(node): + node.set_state(NODE_FAILED) parents = node.waiting_parents to_visit = to_visit | parents pending_children = pending_children - parents diff --git a/src/engine/SCons/Tool/dmd.py b/src/engine/SCons/Tool/dmd.py index a173162..53432c6 100644 --- a/src/engine/SCons/Tool/dmd.py +++ b/src/engine/SCons/Tool/dmd.py @@ -194,7 +194,7 @@ def generate(env): env['SMART_LINKCOM'] = smart_link[linkcom] except KeyError: def _smartLink(source, target, env, for_signature, - defaultLinker=linkcom): + defaultLinker=linkcom, dc=dc): if isD(source): try: libs = env['LIBS'] diff --git a/src/engine/SCons/Tool/g77.py b/src/engine/SCons/Tool/g77.py index 058b061..66d3d89 100644 --- a/src/engine/SCons/Tool/g77.py +++ b/src/engine/SCons/Tool/g77.py @@ -48,8 +48,8 @@ def generate(env): env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS') env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS') else: - env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$SHFORTRANFLAGS -fPIC') - env['SHF77FLAGS'] = SCons.Util.CLVar('$SHF77FLAGS -fPIC') + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -fPIC') + env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS -fPIC') env['FORTRAN'] = fcomp env['SHFORTRAN'] = '$FORTRAN' diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py index db5d06e..020a6f7 100644 --- a/src/engine/SCons/Tool/intelc.py +++ b/src/engine/SCons/Tool/intelc.py @@ -394,7 +394,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0): if version is None: version = '' # Each path has a registry entry, use that or default to subdir - for p in paths.keys(): + for p in paths: try: path=get_intel_registry_value(p[1], version, abi) # These paths may have $(ICInstallDir) diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py index a1067c0..1a42d62 100644 --- a/src/engine/SCons/Tool/msvc.py +++ b/src/engine/SCons/Tool/msvc.py @@ -203,6 +203,11 @@ def _parse_msvc8_overrides(version,platform,suite): user_settings = doc.getElementsByTagName('UserSettings')[0] tool_options = user_settings.getElementsByTagName('ToolsOptions')[0] tool_options_categories = tool_options.getElementsByTagName('ToolsOptionsCategory') + environment_var_map = { + 'IncludeDirectories' : 'INCLUDE', + 'LibraryDirectories' : 'LIBRARY', + 'ExecutableDirectories' : 'PATH', + } for category in tool_options_categories: category_name = category.attributes.get('name') if category_name is not None and category_name.value == 'Projects': @@ -215,21 +220,21 @@ def _parse_msvc8_overrides(version,platform,suite): property_name = property.attributes.get('name') if property_name is None: continue - elif property_name.value == 'IncludeDirectories': - include_dirs = property.childNodes[0].data - # ToDo: Support for other destinations than Win32 - include_dirs = include_dirs.replace('Win32|', '') - dirs['INCLUDE'] = include_dirs - elif property_name.value == 'LibraryDirectories': - lib_dirs = property.childNodes[0].data.replace('Win32|', '') - # ToDo: Support for other destinations than Win32 - lib_dirs = lib_dirs.replace('Win32|', '') - dirs['LIBRARY'] = lib_dirs - elif property_name.value == 'ExecutableDirectories': - path_dirs = property.childNodes[0].data.replace('Win32|', '') - # ToDo: Support for other destinations than Win32 - path_dirs = path_dirs.replace('Win32|', '') - dirs['PATH'] = path_dirs + var_name = environment_var_map.get(property_name) + if var_name: + data = property.childNodes[0].data + value_list = string.split(data, '|') + if len(value_list) == 1: + dirs[var_name] = value_list[0] + else: + while value_list: + dest, value = value_list[:2] + del value_list[:2] + # ToDo: Support for destinations + # other than Win32 + if dest == 'Win32': + dirs[var_name] = value + break else: # There are no default directories in the registry for VS8 Express :( raise SCons.Errors.InternalError, "Unable to find MSVC paths in the registry." diff --git a/src/script/sconsign.py b/src/script/sconsign.py index 2aebf85..7591069 100644 --- a/src/script/sconsign.py +++ b/src/script/sconsign.py @@ -274,6 +274,8 @@ def nodeinfo_raw(name, ninfo, prefix=""): l = [] for k in keys: l.append('%s: %s' % (repr(k), repr(d.get(k)))) + if '\n' in name: + name = repr(name) return name + ': {' + string.join(l, ', ') + '}' def nodeinfo_cooked(name, ninfo, prefix=""): @@ -282,6 +284,8 @@ def nodeinfo_cooked(name, ninfo, prefix=""): except AttributeError: field_list = [] f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v) + if '\n' in name: + name = repr(name) outlist = [name+':'] + filter(None, map(f, field_list)) if Verbose: sep = '\n ' + prefix diff --git a/test/Actions/function.py b/test/Actions/function.py index 5e755a8..4fc0dc4 100644 --- a/test/Actions/function.py +++ b/test/Actions/function.py @@ -60,7 +60,33 @@ optEnv = Environment(options=options, tools=[]) r = re.compile(optEnv['regexp']) -toto = \ +withClosure = \ +r''' +def toto(header='%(header)s', trailer='%(trailer)s'): + xxx = %(closure_cell_value)s + def writeDeps(target, source, env, b=%(b)s, r=r %(extraarg)s , + header=header, trailer=trailer): + """+'"""%(docstring)s"""'+""" + def foo(b=b): + return %(nestedfuncexp)s + f = open(str(target[0]),'wb') + f.write(header) + for d in env['ENVDEPS']: + f.write(d+'%(separator)s') + f.write(trailer+'\\n') + f.write(str(foo())+'\\n') + f.write(r.match('aaaa').group(1)+'\\n') + %(extracode)s + try: + f.write(str(xarg)+'\\n') + except NameError: + pass + f.close() + + return writeDeps +''' + +NoClosure = \ r''' def toto(header='%(header)s', trailer='%(trailer)s'): xxx = %(closure_cell_value)s @@ -86,7 +112,17 @@ def toto(header='%(header)s', trailer='%(trailer)s'): return writeDeps ''' -exec( toto % optEnv ) +try: + # Check that lexical closure are supported + def a(): + x = 0 + def b(): + return x + return b + a().func_closure[0].cell_contents + exec( withClosure % optEnv ) +except (AttributeError, TypeError): + exec( NoClosure % optEnv ) genHeaderBld = SCons.Builder.Builder( action = SCons.Action.Action( @@ -122,15 +158,25 @@ scons: `.' is up to date. scons: done building targets. """ -def runtest( arguments, expectedOutFile, expectedRebuild=True): +import sys +if sys.version[:3] == '2.1': + expectedStderr = """\ +%s:79: SyntaxWarning: local name 'x' in 'a' shadows use of 'x' as global in nested scope 'b' + def a(): +""" % test.workpath('SConstruct') +else: + expectedStderr = "" + +def runtest(arguments, expectedOutFile, expectedRebuild=True, stderr=expectedStderr): test.run(arguments=arguments, - stdout=expectedRebuild and rebuildstr or nobuildstr) + stdout=expectedRebuild and rebuildstr or nobuildstr, + stderr=expectedStderr) test.must_match('Out.gen.h', expectedOutFile) # Should not be rebuild when ran a second time with the same # arguments. - test.run(arguments = arguments, stdout=nobuildstr) + test.run(arguments = arguments, stdout=nobuildstr, stderr=expectedStderr) test.must_match('Out.gen.h', expectedOutFile) diff --git a/test/Configure/build-fail.py b/test/Configure/build-fail.py new file mode 100644 index 0000000..2facb81 --- /dev/null +++ b/test/Configure/build-fail.py @@ -0,0 +1,90 @@ +#!/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__" + +""" +Verify that Configure tests work even after an earlier test fails. + +This was broken in 0.98.3 because we'd mark the /usr/bin/g++ compiler +as having failed (because it was on the candidates list as the implicit +command dependency for both the object file and executable generated +for the configuration test) and then avoid trying to rebuild anything +else that used the "failed" Node. + +Thanks to Ben Webb for the test case. +""" + +import os + +import TestSCons + +_obj = TestSCons._obj + +test = TestSCons.TestSCons(match = TestSCons.match_re_dotall) + +test.subdir('a', 'b') + +a_boost_hpp = os.path.join('..', 'a', 'boost.hpp') +b_boost_hpp = os.path.join('..', 'b', 'boost.hpp') + +test.write('SConstruct', """\ +import os +def _check(context): + for dir in ['a', 'b']: + inc = os.path.join('..', dir, 'boost.hpp') + result = context.TryRun(''' + #include "%s" + + int main() { return 0; } + ''' % inc, '.cpp')[0] + if result: + import sys + sys.stdout.write('%s: ' % inc) + break + context.Result(result) + return result +env = Environment() +conf = env.Configure(custom_tests={'CheckBoost':_check}) +conf.CheckBoost() +conf.Finish() +""") + +test.write(['b', 'boost.hpp'], """#define FILE "b/boost.hpp"\n""") + +expect = test.wrap_stdout(read_str = "%s: yes\n" % b_boost_hpp, + build_str = "scons: `.' is up to date.\n") + +test.run(arguments='--config=force', stdout=expect) + +expect = test.wrap_stdout(read_str = "%s: yes\n" % a_boost_hpp, + build_str = "scons: `.' is up to date.\n") + +test.write(['a', 'boost.hpp'], """#define FILE "a/boost.hpp"\n""") + +test.run(arguments='--config=force', stdout=expect) + +test.run() + +test.pass_test() diff --git a/test/Configure/implicit-cache.py b/test/Configure/implicit-cache.py new file mode 100644 index 0000000..58ae7c9 --- /dev/null +++ b/test/Configure/implicit-cache.py @@ -0,0 +1,103 @@ +#!/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__" + +""" +Verify that use of --implicit-cache with the Python Value Nodes +used by the Configure subsystem generate the same .sconsign file +and don't cause it to grow without limit. + +This was reported as issue 2033 in the tigris.org bug tracker, by the +Ardour project. Prior to 0.98.4, the Value implementation would actually +return the repr() of its value as the str(). This was done because +it made saving a Value in a file and reading it back in kind of work, +because a print a string Value into a file (for example) would in fact +put quotes around it and be assignable in that file. + +The problem is that this would get stored in a .sconsign file as its +repr(), with the specific problem being that Values with embedded newlines +would get stored as strings containing backslash+n digraphs *and* the +quotes at beginning and end of the string:: + + '\n#include <math.h>\n\n': {<.sconsign info>} + +Then, when we read that back in from the .sconsign file, we would store +that repr() as a string Value itself, escaping the backslashes and +including the quotes, so when we stored it the second time it would end +up looking like: + + "'\\n#include <math.h>\\n\\n'": {<.sconsign info>} + +Every time that we would read this value and store it again (because +something else changed in the .sconf_temp directory), the string would +get longer and longer until it blew out the users's memory. +""" + +import TestSConsign + +test = TestSConsign.TestSConsign() + +test.write('SConstruct', """ +env = Environment(CPPPATH=['.']) +conf = Configure(env) +conf.CheckHeader( 'math.h' ) +if ARGUMENTS.get('USE_FOO'): + conf.CheckHeader( 'foo.h' ) +env = conf.Finish() +""") + +test.write('foo.h', "#define FOO 1\n") + +# First run: Have the configure subsystem only look for math.h, and +# squirrel away the .sconsign info for the conftest_0.c file that's +# generated from the Python Value Node that we're using for our test. + +test.run(arguments = '.') + +test.run_sconsign('-d .sconf_temp -e conftest_0.c --raw .sconsign.dblite') +old_sconsign_dblite = test.stdout() + +# Second run: Have the configure subsystem also look for foo.h, so +# that there's a change in the .sconf_temp directory that will cause its +# .sconsign information to get rewritten from disk. Squirrel away the +# .sconsign info for the conftest_0.c file. The now-fixed bug would show +# up because the entry would change with the additional string-escaping +# described above. The now-correct behavior is that the re-stored value +# for conftest_0.c doesn't change. + +test.run(arguments = '--implicit-cache USE_FOO=1 .') + +test.run_sconsign('-d .sconf_temp -e conftest_0.c --raw .sconsign.dblite') +new_sconsign_dblite = test.stdout() + +if old_sconsign_dblite != new_sconsign_dblite: + print ".sconsign.dblite did not match:" + print "FIRST RUN ==========" + print old_sconsign_dblite + print "SECOND RUN ==========" + print new_sconsign_dblite + test.fail_test() + +test.pass_test() diff --git a/test/KeyboardInterrupt.py b/test/KeyboardInterrupt.py index 25239b5..5a06119 100644 --- a/test/KeyboardInterrupt.py +++ b/test/KeyboardInterrupt.py @@ -28,15 +28,10 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" Verify that we handle keyboard interrupts (CTRL-C) correctly. """ -import os - import TestSCons test = TestSCons.TestSCons() -if 'killpg' not in dir(os) or 'setpgrp' not in dir(os): - test.skip_test("This Python version does not support killing process groups; skipping test.\n") - test.write('toto.c', r""" void foo() {} @@ -44,20 +39,47 @@ void foo() test.write('SConstruct', r""" import os +import sys import signal -# Make sure that SCons is a process group leader. -os.setpgrp() +if 'killpg' not in dir(os) or 'setpgrp' not in dir(os) or sys.platform == 'cygwin': + # The platform does not support process group. Therefore, we + # directly invoked the SIGINT handler to simulate a + # KeyboardInterrupt. This hack is necessary because there is no + # easy way to get access to the current Job/Taskmaster object. + # + # Note that this way of performing the test is not as good as + # using killpg because the Taskmaster is stopped synchronously. In + # addition, the SCons subprocesses (or forked children before the + # exec() of the subprocess) are never killed. This therefore + # exercise less SCons functionality. + # + # FIXME: There seems to be a bug on Cygwin where the compiler + # process hangs after sending the SIGINT signal to the process + # group. It is probably a bug in cygwin1.dll, or maybe in the + # Python 'C' code or the Python subprocess module. We therefore do + # not use 'killpg' on Cygwin. + def explode(env, target, source): + handler = signal.getsignal(signal.SIGINT) + handler(signal.SIGINT, None) + return 0 +else: + # The platform does support process group so we use killpg to send + # a SIGINT to everyone. + + # Make sure that SCons is a process group leader. + os.setpgrp() + + def explode(env, target, source): + os.killpg(0, signal.SIGINT) -all = [] -def explode(env, target, source): - os.killpg(0, signal.SIGINT) +all = [] for i in xrange(40): - all += Object('toto%5d' % i, 'toto.c') + all.extend(Object('toto%5d' % i, 'toto.c')) -all+= Command( 'broken', 'toto.c', explode) +all.extend(Command( 'broken', 'toto.c', explode)) Default( Alias('all', all)) """ diff --git a/test/Value.py b/test/Value.py index 72d499e..01647e3 100644 --- a/test/Value.py +++ b/test/Value.py @@ -91,12 +91,12 @@ for source_signature in ['MD5', 'timestamp-newer']: test.run(arguments='-c') test.run() - out7 = """create_value(["'my value'"], ["f3.out"])""" - out8 = """create_value_file(["f5.out"], ["'my value'"])""" + out7 = """create_value(['my value'], ["f3.out"])""" + out8 = """create_value_file(["f5.out"], ['my value'])""" - out1 = """create(["f1.out"], ["'/usr/local'"])""" - out2 = """create(["f2.out"], ["10"])""" - out3 = """create\\(\\["f3.out"\\], \\["<.*.Custom instance at """ + out1 = """create(["f1.out"], ['/usr/local'])""" + out2 = """create(["f2.out"], [10])""" + out3 = """create\\(\\["f3.out"\\], \\[<.*.Custom instance at """ #" <- unconfuses emacs syntax highlighting test.fail_test(string.find(test.stdout(), out1) == -1) @@ -114,9 +114,9 @@ for source_signature in ['MD5', 'timestamp-newer']: test.up_to_date(arguments='.') test.run(arguments='prefix=/usr') - out4 = """create(["f1.out"], ["'/usr'"])""" - out5 = """create(["f2.out"], ["4"])""" - out6 = """create\\(\\["f3.out"\\], \\["<.*.Custom instance at """ + out4 = """create(["f1.out"], ['/usr'])""" + out5 = """create(["f2.out"], [4])""" + out6 = """create\\(\\["f3.out"\\], \\[<.*.Custom instance at """ #" <- unconfuses emacs syntax highlighting test.fail_test(string.find(test.stdout(), out4) == -1) test.fail_test(string.find(test.stdout(), out5) == -1) @@ -132,7 +132,7 @@ for source_signature in ['MD5', 'timestamp-newer']: test.unlink('f3.out') test.run(arguments='prefix=/var') - out4 = """create(["f1.out"], ["'/var'"])""" + out4 = """create(["f1.out"], ['/var'])""" test.fail_test(string.find(test.stdout(), out4) == -1) test.fail_test(string.find(test.stdout(), out5) != -1) diff --git a/test/explain/basic.py b/test/explain/basic.py index e1e3aec..e1198d5 100644 --- a/test/explain/basic.py +++ b/test/explain/basic.py @@ -39,9 +39,10 @@ test = TestSCons.TestSCons() test.subdir(['src'], ['src', 'subdir']) -subdir_file8 = os.path.join('subdir', 'file8') subdir_file7 = os.path.join('subdir', 'file7') subdir_file7_in = os.path.join('subdir', 'file7.in') +subdir_file8 = os.path.join('subdir', 'file8') +subdir_file9 = os.path.join('subdir', 'file9') cat_py = test.workpath('cat.py') inc_aaa = test.workpath('inc', 'aaa') @@ -122,6 +123,7 @@ file6 = env.Cat('file6', 'file6.in') AlwaysBuild(file6) env.Cat('subdir/file7', 'subdir/file7.in') env.OneCat('subdir/file8', ['subdir/file7.in', env.Value(%(test_value)s)] ) +env.OneCat('subdir/file9', ['subdir/file7.in', env.Value(7)] ) """ % valueDict ) test_value = '"first"' @@ -195,6 +197,8 @@ scons: building `%(subdir_file7)s' because it doesn't exist %(_python_)s %(cat_py)s %(subdir_file7)s %(subdir_file7_in)s scons: building `%(subdir_file8)s' because it doesn't exist %(_python_)s %(cat_py)s %(subdir_file8)s %(subdir_file7_in)s +scons: building `%(subdir_file9)s' because it doesn't exist +%(_python_)s %(cat_py)s %(subdir_file9)s %(subdir_file7_in)s """ % locals()) test.run(chdir='src', arguments=args, stdout=expect) @@ -248,8 +252,8 @@ scons: rebuilding `file5' because `%(inc_bbb_k)s' changed scons: rebuilding `file6' because AlwaysBuild() is specified %(_python_)s %(cat_py)s file6 file6.in scons: rebuilding `%(subdir_file8)s' because: - `"'first'"' is no longer a dependency - `'second'' is a new dependency + `first' is no longer a dependency + `second' is a new dependency %(_python_)s %(cat_py)s %(subdir_file8)s %(subdir_file7_in)s """ % locals()) |