1. SCons
  2. Core
  3. SCons

Commits

Gary Oberbrunner  committed f059ebe Merge

Merge pull request #88: various usability enhancements
* Allow multiple --debug= values
* Add support for a readonly cache (--cache-readonly)
* Always print stats if requested
* Generally try harder to print out a message on build errors

  • Participants
  • Parent commits ea09c38, e207d15
  • Branches default

Comments (0)

Files changed (12)

File doc/man/scons.xml

View file
   </listitem>
   </varlistentry>
   <varlistentry>
+  <term>--warn=target_not_build, --warn=no-target_not_built</term>
+  <listitem>
+<para>Enables or disables warnings about a build rule not building the
+ expected targets. These warnings are not currently enabled by default.</para>
+
+  </listitem>
+  </varlistentry>
+  <varlistentry>
   <term>-Y<emphasis> repository</emphasis>, --repository=<emphasis>repository</emphasis>, --srcdir=<emphasis>repository</emphasis></term>
   <listitem>
 <para>Search the specified repository for any input and target

File runtest.py

View file
 #                       command line it will execute before
 #                       executing it.  This suppresses that print.
 #
+#       --quit-on-failure Quit on any test failure
+#
 #       -s              Short progress.  Prints only the command line
 #                       and a percentage value, based on the total and
 #                       current number of tests.
 suppress_stdout = False
 suppress_stderr = False
 allow_pipe_files = True
+quit_on_failure = False
 
 helpstr = """\
 Usage: runtest.py [OPTIONS] [TEST ...]
   --passed                    Summarize which tests passed.
   --qmtest                    Run using the QMTest harness (deprecated).
   -q, --quiet                 Don't print the test being executed.
+  --quit-on-failure           Quit on any test failure
   -s, --short-progress        Short progress, prints only the command line
                               and a percentage value, based on the total and
                               current number of tests.
                              'jobs=',
                              'list', 'no-exec', 'nopipefiles',
                              'package=', 'passed', 'python=', 'qmtest',
-                             'quiet', 'short-progress', 'time',
+                             'quiet',
+                             'quit-on-failure',
+                             'short-progress', 'time',
                              'version=', 'exec=',
                              'verbose='])
 
     elif o in ['-q', '--quiet']:
         printcommand = 0
         suppress_stdout = True
-        suppress_stderr = True        
+        suppress_stderr = True
+    elif o in ['--quit-on-failure']:
+        quit_on_failure = True
     elif o in ['-s', '--short-progress']:
         print_progress = 1
         suppress_stdout = True
     # Else, we catch the output of both pipes...
     if allow_pipe_files:
         # The subprocess.Popen() suffers from a well-known
-        # problem. Data for stdout/stderr is read into a 
+        # problem. Data for stdout/stderr is read into a
         # memory buffer of fixed size, 65K which is not very much.
         # When it fills up, it simply stops letting the child process
         # write to it. The child will then sit and patiently wait to
-        # be able to write the rest of its output. Hang! 
+        # be able to write the rest of its output. Hang!
         # In order to work around this, we follow a suggestion
         # by Anders Pearson in
         #   http://http://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
         # and pass temp file objects to Popen() instead of the ubiquitous
-        # subprocess.PIPE. 
+        # subprocess.PIPE.
         def spawn_it(command_args):
             # Create temporary files
             import tempfile
                                  shell=True)
             # ... and wait for it to finish.
             ret = p.wait()
-            
+
             try:
                 # Rewind to start of files
                 tmp_stdout.seek(0)
                 # Remove temp files by closing them
                 tmp_stdout.close()
                 tmp_stderr.close()
-                
+
             # Return values
             return (spawned_stderr, spawned_stdout, ret)
-        
+
     else:
         # We get here only if the user gave the '--nopipefiles'
         # option, meaning the "temp file" approach for
         # potential deadlock situation in the following code:
         #   If the subprocess writes a lot of data to its stderr,
         #   the pipe will fill up (nobody's reading it yet) and the
-        #   subprocess will wait for someone to read it. 
+        #   subprocess will wait for someone to read it.
         #   But the parent process is trying to read from stdin
-        #   (but the subprocess isn't writing anything there).  
+        #   (but the subprocess isn't writing anything there).
         #   Hence a deadlock.
         # Be dragons here! Better don't use this!
         def spawn_it(command_args):
                                  shell=True)
             # ... and wait for it to finish.
             self.status = p.wait()
-            
+
             try:
                 # Rewind to start of files
                 tmp_stdout.seek(0)
                 # Remove temp files by closing them
                 tmp_stdout.close()
                 tmp_stderr.close()
-    else:        
+    else:
         def execute(self):
             p = subprocess.Popen(self.command_str,
                                  stdout=subprocess.PIPE,
     if head:
         os.environ['PYTHON_SCRIPT_DIR'] = head
     else:
-        os.environ['PYTHON_SCRIPT_DIR'] = ''     
+        os.environ['PYTHON_SCRIPT_DIR'] = ''
     test_start_time = time_func()
     if execute_tests:
         t.execute()
     print_time_func("Test execution time: %.1f seconds\n", t.test_time)
     if io_lock:
         io_lock.release()
+    if quit_on_failure and t.status == 1:
+        print "Exiting due to error"
+        print t.status
+        sys.exit(1)
 
 class RunTest(threading.Thread):
     def __init__(self, queue, io_lock):

File src/engine/SCons/Environment.py

View file
             #  -symbolic       (linker global binding)
             #  -R dir          (deprecated linker rpath)
             # IBM compilers may also accept -qframeworkdir=foo
-    
+
             params = shlex.split(arg)
             append_next_arg_to = None   # for multi-word args
             for arg in params:
                     append_next_arg_to = arg
                 else:
                     dict['CCFLAGS'].append(arg)
-    
+
         for arg in flags:
             do_parse(arg)
         return dict
 
 #     def MergeShellPaths(self, args, prepend=1):
 #         """
-#         Merge the dict in args into the shell environment in env['ENV'].  
+#         Merge the dict in args into the shell environment in env['ENV'].
 #         Shell path elements are appended or prepended according to prepend.
 
 #         Uses Pre/AppendENVPath, so it always appends or prepends uniquely.
             platform = SCons.Platform.Platform(platform)
         self._dict['PLATFORM'] = str(platform)
         platform(self)
-        
+
         self._dict['HOST_OS']      = self._dict.get('HOST_OS',None)
         self._dict['HOST_ARCH']    = self._dict.get('HOST_ARCH',None)
-        
+
         # Now set defaults for TARGET_{OS|ARCH}
         self._dict['TARGET_OS']      = self._dict.get('TARGET_OS',None)
         self._dict['TARGET_ARCH']    = self._dict.get('TARGET_ARCH',None)
-        
+
 
         # Apply the passed-in and customizable variables to the
         # environment before calling the tools, because they may use
             # "continue" statements whenever we finish processing an item,
             # but Python 1.5.2 apparently doesn't let you use "continue"
             # within try:-except: blocks, so we have to nest our code.
-            try:                
+            try:
                 if key == 'CPPDEFINES' and SCons.Util.is_String(self._dict[key]):
                     self._dict[key] = [self._dict[key]]
                 orig = self._dict[key]
                             orig = orig.items()
                             orig += val
                             self._dict[key] = orig
-                        else:    
+                        else:
                             for v in val:
                                 orig[v] = None
                     else:
             path = str(self.fs.Dir(path))
         return path
 
-    def AppendENVPath(self, name, newpath, envname = 'ENV', 
+    def AppendENVPath(self, name, newpath, envname = 'ENV',
                       sep = os.pathsep, delete_existing=1):
         """Append path elements to the path 'name' in the 'ENV'
         dictionary for this environment.  Will only add any particular
                         dk = dk.items()
                     elif SCons.Util.is_String(dk):
                         dk = [(dk,)]
-                    else:                    
+                    else:
                         tmp = []
                         for i in dk:
                             if SCons.Util.is_List(i):
                             dk = filter(lambda x, val=val: x not in val, dk)
                             self._dict[key] = dk + val
                         else:
-                            dk = [x for x in dk if x not in val]                
+                            dk = [x for x in dk if x not in val]
                             self._dict[key] = dk + val
                     else:
                         # By elimination, val is not a list.  Since dk is a
             builders = self._dict['BUILDERS']
         except KeyError:
             pass
-            
+
         clone = copy.copy(self)
         # BUILDERS is not safe to do a simple copy
         clone._dict = semi_deepcopy_dict(self._dict, ['BUILDERS'])
         apply_tools(clone, tools, toolpath)
 
         # apply them again in case the tools overwrote them
-        clone.Replace(**new)        
+        clone.Replace(**new)
 
         # Finally, apply any flags to be merged in
         if parse_flags: clone.MergeFlags(parse_flags)
             t.set_precious()
         return tlist
 
+    def Pseudo(self, *targets):
+        tlist = []
+        for t in targets:
+            tlist.extend(self.arg2nodes(t, self.fs.Entry))
+        for t in tlist:
+            t.set_pseudo()
+        return tlist
+
     def Repository(self, *dirs, **kw):
         dirs = self.arg2nodes(list(dirs), self.fs.Dir)
         self.fs.Repository(*dirs, **kw)

File src/engine/SCons/EnvironmentTests.py

View file
                                                single_source = 1)
             kw['BUILDERS'] = {'Object' : static_obj}
             static_obj.add_action('.cpp', 'fake action')
-            
+
         env = Environment(*args, **kw)
         return env
 
         assert env['DDD1'] == ['c', 'a', 'b'], env['DDD1'] # a & b move to end
         env.AppendUnique(DDD1 = ['e','f', 'e'], delete_existing=1)
         assert env['DDD1'] == ['c', 'a', 'b', 'f', 'e'], env['DDD1'] # add last
-        
+
         env['CLVar'] = CLVar([])
         env.AppendUnique(CLVar = 'bar')
         result = env['CLVar']
 
         try:
             save_command = []
-            env.backtick = my_backtick(save_command, 
+            env.backtick = my_backtick(save_command,
                                  "-I/usr/include/fum -I bar -X\n" + \
                                  "-L/usr/fax -L foo -lxxx -l yyy " + \
                                  "-Wa,-as -Wl,-link " + \
         env.PrependUnique(DDD1 = ['a','c'], delete_existing=1)
         assert env['DDD1'] == ['a', 'c', 'b'], env['DDD1'] # a & c move to front
         env.PrependUnique(DDD1 = ['d','e','d'], delete_existing=1)
-        assert env['DDD1'] == ['d', 'e', 'a', 'c', 'b'], env['DDD1'] 
+        assert env['DDD1'] == ['d', 'e', 'a', 'c', 'b'], env['DDD1']
 
 
         env['CLVar'] = CLVar([])
         assert t[4].path == 'p_ggg'
         assert t[4].precious
 
+    def test_Pseudo(self):
+        """Test the Pseudo() method"""
+        env = self.TestEnvironment(FOO='ggg', BAR='hhh')
+        env.Dir('p_hhhb')
+        env.File('p_d')
+        t = env.Pseudo('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
+
+        assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__
+        assert t[0].path == 'p_a'
+        assert t[0].pseudo
+        assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__
+        assert t[1].path == 'p_hhhb'
+        assert t[1].pseudo
+        assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__
+        assert t[2].path == 'p_c'
+        assert t[2].pseudo
+        assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__
+        assert t[3].path == 'p_d'
+        assert t[3].pseudo
+        assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__
+        assert t[4].path == 'p_ggg'
+        assert t[4].pseudo
+
     def test_Repository(self):
         """Test the Repository() method."""
         class MyFS(object):

File src/engine/SCons/Node/NodeTests.py

View file
         node.set_precious(7)
         assert node.precious == 7
 
+    def test_set_pseudo(self):
+        """Test setting a Node's pseudo value
+        """
+        node = SCons.Node.Node()
+        node.set_pseudo()
+        assert node.pseudo
+        node.set_pseudo(False)
+        assert not node.pseudo
+
     def test_exists(self):
         """Test evaluating whether a Node exists.
         """
                 self.source_scanner = scanner
 
         builder = Builder2(ts1)
-            
+
         targets = builder([source])
         s = targets[0].get_source_scanner(source)
         assert s is ts1, s
         builder = Builder1(env=Environment(SCANNERS = [ts3]))
 
         targets = builder([source])
-        
+
         s = targets[0].get_source_scanner(source)
         assert s is ts3, s
 

File src/engine/SCons/Node/__init__.py

View file
 def classname(obj):
     return str(obj.__class__).split('.')[-1]
 
+# Set to false if we're doing a dry run. There's more than one of these
+# little treats
+do_store_info = True
+
 # Node states
 #
 # These are in "priority" order, so that the maximum value for any
         self.env = None
         self.state = no_state
         self.precious = None
+        self.pseudo = False
         self.noclean = 0
         self.nocache = 0
         self.cached = 0 # is this node pulled from cache?
 
         self.clear()
 
+        if self.pseudo:
+            if self.exists():
+                raise SCons.Errors.UserError("Pseudo target " + str(self) + " must not exist")
+        else:
+            if not self.exists() and do_store_info:
+                SCons.Warnings.warn(SCons.Warnings.TargetNotBuiltWarning,
+                                    "Cannot find target " + str(self) + " after building")
         self.ninfo.update(self)
 
     def visited(self):
         """Set the Node's precious value."""
         self.precious = precious
 
+    def set_pseudo(self, pseudo = True):
+        """Set the Node's precious value."""
+        self.pseudo = pseudo
+
     def set_noclean(self, noclean = 1):
         """Set the Node's noclean value."""
         # Make sure noclean is an integer so the --debug=stree

File src/engine/SCons/Script/Main.py

View file
     SCons.Action.print_actions          = not options.silent
     SCons.Action.execute_actions        = not options.no_exec
     SCons.Node.FS.do_store_info         = not options.no_exec
+    SCons.Node.do_store_info            = not options.no_exec
     SCons.SConf.dryrun                  = options.no_exec
 
     if options.diskcheck:

File src/engine/SCons/Script/Main.xml

View file
 </summary>
 </scons_function>
 
+<scons_function name="Pseudo">
+<arguments>
+(target, ...)
+</arguments>
+<summary>
+<para>
+This indicates that each given
+<varname>target</varname>
+should not be created by the build rule, and if the target is created,
+an error will be generated. This is similar to the gnu make .PHONY
+target. However, in the vast majority of cases, an
+&f-Alias;
+is more appropriate.
+
+Multiple targets can be passed in to a single call to
+&f-Pseudo;.
+</para>
+</summary>
+</scons_function>
 <scons_function name="SetOption">
 <arguments>
 (name, value)
 </summary>
 </scons_function>
 
-</sconsdoc>
+</sconsdoc>

File src/engine/SCons/Warnings.py

View file
 
 # NOTE:  If you add a new warning class, add it to the man page, too!
 
+class TargetNotBuiltWarning(Warning): # Should go to OnByDefault
+    pass
+
 class CacheWriteErrorWarning(Warning):
     pass
 

File test/Pseudo.py

View file
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+# Firstly, build a pseudo target and make sure we get no warnings it
+# doesn't exist under any circumstances
+test.write('SConstruct', """
+env = Environment()
+env.Pseudo(env.Command('foo.out', [], '@echo boo'))
+""")
+
+test.run(arguments='-Q', stdout = 'boo\n')
+
+test.run(arguments='-Q --warning=target-not-built', stdout = "boo\n")
+
+# Now do the same thing again but create the target and check we get an
+# error if it exists after the build
+test.write('SConstruct', """
+env = Environment()
+env.Pseudo(env.Command('foo.out', [], Touch('$TARGET')))
+""")
+
+test.run(arguments='-Q', stdout = 'Touch("foo.out")\n', stderr = None,
+         status = 2)
+test.must_contain_all_lines(test.stderr(),
+                            'scons:  *** Pseudo target foo.out must not exist')
+test.run(arguments='-Q --warning=target-not-built',
+         stdout = 'Touch("foo.out")\n',
+         stderr = None, status = 2)
+test.must_contain_all_lines(test.stderr(),
+                            'scons:  *** Pseudo target foo.out must not exist')
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:

File test/option/warn-dependency.py

View file
 env['BUILDERS']['test'] = Builder(action=build,
                                   source_scanner=SCons.Defaults.ObjSourceScan)
 env.test(target='foo', source='foo.c')
+env.Pseudo('foo')
 """)
 
 test.write("foo.c","""

File test/warning-TargetNotBuiltWarning.py

View file
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+foo = Command('foo.out', [], '@echo boo')
+bill = Command('bill.out', [], Touch('$TARGET'))
+Depends(bill, foo)
+Alias('jim', bill)
+""")
+
+test.run(arguments='-Q jim', stdout = 'boo\nTouch("bill.out")\n')
+
+test.run(arguments='-Q jim --warning=target-not-built',
+         stdout = "boo\nscons: `jim' is up to date.\n",
+         stderr = None)
+test.must_contain_all_lines(test.stderr(),
+                            'scons: warning: Cannot find target foo.out after building')
+
+test.run(arguments='-Q jim --warning=target-not-built -n',
+         stdout = "scons: `jim' is up to date.\n")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: