Commits

Gary Oberbrunner  committed 6c79b72 Merge

Merge pull request #88 (for real this time). Prev commit was actually #87.
* Allow multiple options to be specified with --debug=a,b,c
* 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 f059ebe, b8f89b5

Comments (0)

Files changed (7)

File QMTest/TestSCons.py

         self.run(program = python, stdin = """\
 import os, sys
 try:
-	py_ver = 'python%d.%d' % sys.version_info[:2]
+    py_ver = 'python%d.%d' % sys.version_info[:2]
 except AttributeError:
-	py_ver = 'python' + sys.version[:3]
+    py_ver = 'python' + sys.version[:3]
 print os.path.join(sys.prefix, 'include', py_ver)
 print os.path.join(sys.prefix, 'lib', py_ver, 'config')
 print py_ver
         options = kw.get('options', '')
         if additional is not None:
             options += additional
-        kw['options'] = options + ' --debug=memory --debug=time'
+        kw['options'] = options + ' --debug=memory,time'
 
     def startup(self, *args, **kw):
         """

File doc/man/scons.xml

 
     <releaseinfo>version &buildversion;</releaseinfo>
   </referenceinfo>
-  
+
   <title>SCons &buildversion;</title>
   <subtitle>MAN page</subtitle>
-  
-  
-<refentry id='scons1'> 
+
+
+<refentry id='scons1'>
 <refmeta>
 <refentrytitle>SCONS</refentrytitle>
 <manvolnum>1</manvolnum>
 <!-- body begins here -->
 <refsynopsisdiv id='synopsis'>
 <cmdsynopsis>
-  <command>scons</command>    
+  <command>scons</command>
     <arg choice='opt' rep='repeat'><replaceable>options</replaceable></arg>
     <arg choice='opt' rep='repeat'><replaceable>name=val</replaceable></arg>
     <arg choice='opt' rep='repeat'><replaceable>targets</replaceable></arg>
 Caching behavior may be disabled and controlled in other ways by the
 <option>--cache-force</option>,
 <option>--cache-disable</option>,
+<option>--cache-readonly</option>,
 and
 <option>--cache-show</option>
 command-line options.  The
 
   </listitem>
   </varlistentry>
+<varlistentry>
+  <term>--cache-readonly</term>
+  <listitem>
+<para>Use the cache (if enabled) for reading, but do not not update the
+cache with changed files.
+</para>
+
+  </listitem>
+  </varlistentry>
   <varlistentry>
   <term>--cache-show</term>
   <listitem>
   <term>--debug=<emphasis>type</emphasis></term>
   <listitem>
 <para>Debug the build process.
-<emphasis>type</emphasis>
-specifies what type of debugging:</para>
+<emphasis>type[,type...]</emphasis>
+specifies what type of debugging. Multiple types may be specified,
+separated by commas. The following types are valid:</para>
 
   </listitem>
   </varlistentry>
 --cache-debug=FILE
 --cache-disable, --no-cache
 --cache-force, --cache-populate
+--cache-readonly
 --cache-show
 --debug=TYPE
 -i, --ignore-errors
 <emphasis role="bold">$MYPATH</emphasis>
 construction variable. It lets SCons detect the file
 <emphasis role="bold">incs/foo.inc</emphasis>
-, even if 
+, even if
 <emphasis role="bold">foo.x</emphasis>
 contains the line
 <emphasis role="bold">include foo.inc</emphasis>

File src/engine/SCons/CacheDir.py

 cache_debug = False
 cache_force = False
 cache_show = False
+cache_readonly = False
 
 def CacheRetrieveFunc(target, source, env):
     t = target[0]
 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
 
 def CachePushFunc(target, source, env):
+    if cache_readonly: return
+
     t = target[0]
     if t.nocache:
         return
     def is_enabled(self):
         return (cache_enabled and not self.path is None)
 
+    def is_readonly(self):
+        return cache_readonly
+
     def cachepath(self, node):
         """
         """
         return False
 
     def push(self, node):
-        if not self.is_enabled():
+        if self.is_readonly() or not self.is_enabled():
             return
         return CachePush(node, [], node.get_build_env())
 

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

     import SCons.Platform.win32
     return SCons.Platform.win32.parallel_msg
 
-#
+def revert_io():
+    # This call is added to revert stderr and stdout to the original
+    # ones just in case some build rule or something else in the system
+    # has redirected them elsewhere.
+    sys.stderr = sys.__stderr__
+    sys.stdout = sys.__stdout__
 
 class SConsPrintHelpException(Exception):
     pass
                (EnvironmentError, SCons.Errors.StopError,
                             SCons.Errors.UserError))):
             type, value, trace = buildError.exc_info
+            if tb and print_stacktrace:
+                sys.stderr.write("scons: internal stack trace:\n")
+                traceback.print_tb(tb, file=sys.stderr)
             traceback.print_exception(type, value, trace)
         elif tb and print_stacktrace:
             sys.stderr.write("scons: internal stack trace:\n")
         # reading SConscript files and haven't started building
         # things yet, stop regardless of whether they used -i or -k
         # or anything else.
+        revert_io()
         sys.stderr.write("scons: *** %s  Stop.\n" % e)
-        exit_status = 2
-        sys.exit(exit_status)
+        sys.exit(2)
     global sconscript_time
     sconscript_time = time.time() - start_time
 
         # Build the targets
         nodes = _build_targets(fs, options, targets, target_top)
         if not nodes:
+            revert_io()
+            print 'Found nothing to build'
             exit_status = 2
 
 def _build_targets(fs, options, targets, target_top):
         SCons.Node.FS.set_diskcheck(options.diskcheck)
 
     SCons.CacheDir.cache_enabled = not options.cache_disable
+    SCons.CacheDir.cache_readonly = options.cache_readonly
     SCons.CacheDir.cache_debug = options.cache_debug
     SCons.CacheDir.cache_force = options.cache_force
     SCons.CacheDir.cache_show = options.cache_show
         prof = Profile()
         try:
             prof.runcall(_main, parser)
-        except SConsPrintHelpException, e:
+        finally:
             prof.dump_stats(options.profile_file)
-            raise e
-        except SystemExit:
-            pass
-        prof.dump_stats(options.profile_file)
     else:
         _main(parser)
 
     OptionsParser = parser
 
     try:
-        _exec_main(parser, values)
+        try:
+            _exec_main(parser, values)
+        finally:
+            revert_io()
     except SystemExit, s:
         if s:
             exit_status = s
         parser.print_help()
         exit_status = 0
     except SCons.Errors.BuildError, e:
+        print e
         exit_status = e.exitstatus
     except:
         # An exception here is likely a builtin Python exception Python

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

 class SConsOptionGroup(optparse.OptionGroup):
     """
     A subclass for SCons-specific option groups.
-    
+
     The only difference between this and the base class is that we print
     the group's help text flush left, underneath their own title but
     lined up with the normal "SCons Options".
     def add_local_option(self, *args, **kw):
         """
         Adds a local option to the parser.
-        
+
         This is initiated by a SetOption() call to add a user-defined
         command-line option.  We add the option to a separate option
         group for the local options, creating the group if necessary.
         out liking:
 
         --  add our own regular expression that doesn't break on hyphens
-            (so things like --no-print-directory don't get broken); 
+            (so things like --no-print-directory don't get broken);
 
         --  wrap the list of options themselves when it's too long
             (the wrapper.fill(opts) call below);
- 
+
         --  set the subsequent_indent when wrapping the help_text.
         """
         # The help for each option consists of two parts:
                   action="store_true",
                   help="Copy already-built targets into the CacheDir.")
 
+    op.add_option('--cache-readonly',
+                  dest='cache_readonly', default=False,
+                  action="store_true",
+                  help="Do not update CacheDir with built targets.")
+
     op.add_option('--cache-show',
                   dest='cache_show', default=False,
                   action="store_true",
         if not value in c_options:
             raise OptionValueError(opt_invalid('config', value, c_options))
         setattr(parser.values, option.dest, value)
+
     opt_config_help = "Controls Configure subsystem: %s." \
                       % ", ".join(config_options)
+
     op.add_option('--config',
                   nargs=1, type="string",
                   dest="config", default="auto",
                      "pdb", "prepare", "presub", "stacktrace",
                      "time"]
 
-    def opt_debug(option, opt, value, parser,
+    def opt_debug(option, opt, value__, parser,
                   debug_options=debug_options,
                   deprecated_debug_options=deprecated_debug_options):
-        if value in debug_options:
-            parser.values.debug.append(value)
-        elif value in deprecated_debug_options.keys():
-            parser.values.debug.append(value)
-            try:
-                parser.values.delayed_warnings
-            except AttributeError:
-                parser.values.delayed_warnings = []
-            msg = deprecated_debug_options[value]
-            w = "The --debug=%s option is deprecated%s." % (value, msg)
-            t = (SCons.Warnings.DeprecatedDebugOptionsWarning, w)
-            parser.values.delayed_warnings.append(t)
-        else:
-            raise OptionValueError(opt_invalid('debug', value, debug_options))
+        for value in value__.split(','):
+            if value in debug_options:
+                parser.values.debug.append(value)
+            elif value in deprecated_debug_options.keys():
+                parser.values.debug.append(value)
+                try:
+                    parser.values.delayed_warnings
+                except AttributeError:
+                    parser.values.delayed_warnings = []
+                msg = deprecated_debug_options[value]
+                w = "The --debug=%s option is deprecated%s." % (value, msg)
+                t = (SCons.Warnings.DeprecatedDebugOptionsWarning, w)
+                parser.values.delayed_warnings.append(t)
+            else:
+                raise OptionValueError(opt_invalid('debug', value, debug_options))
+
     opt_debug_help = "Print various types of debugging information: %s." \
                      % ", ".join(debug_options)
     op.add_option('--debug',

File test/CacheDir/option--cr.py

+#!/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__"
+
+"""
+Test the --cache-readonly option when retrieving derived files from a
+CacheDir. It should retrieve as normal but not update files.
+"""
+
+import os.path
+import shutil
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('cache', 'src')
+
+test.write(['src', 'SConstruct'], """
+def cat(env, source, target):
+    target = str(target[0])
+    open('cat.out', 'ab').write(target + "\\n")
+    f = open(target, "wb")
+    for src in source:
+        f.write(open(str(src), "rb").read())
+    f.close()
+env = Environment(BUILDERS={'Cat':Builder(action=cat)})
+env.Cat('aaa.out', 'aaa.in')
+env.Cat('bbb.out', 'bbb.in')
+env.Cat('ccc.out', 'ccc.in')
+env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
+CacheDir(r'%s')
+""" % test.workpath('cache'))
+
+test.write(['src', 'aaa.in'], "aaa.in\n")
+test.write(['src', 'bbb.in'], "bbb.in\n")
+test.write(['src', 'ccc.in'], "ccc.in\n")
+
+# Verify that a normal build works correctly, and clean up.
+# This should populate the cache with our derived files.
+test.run(chdir = 'src', arguments = '.')
+
+test.must_match(['src', 'all'], "aaa.in\nbbb.in\nccc.in\n")
+test.must_match(['src', 'cat.out'], "aaa.out\nbbb.out\nccc.out\nall\n")
+
+test.up_to_date(chdir = 'src', arguments = '.')
+
+test.run(chdir = 'src', arguments = '-c .')
+test.unlink(['src', 'cat.out'])
+
+# Verify that we now retrieve the derived files from cache,
+# not rebuild them.  Then clean up.
+test.run(chdir = 'src', arguments = '--cache-readonly .',
+         stdout = test.wrap_stdout("""\
+Retrieved `aaa.out' from cache
+Retrieved `bbb.out' from cache
+Retrieved `ccc.out' from cache
+Retrieved `all' from cache
+"""))
+
+test.must_match(['src', 'all'], "aaa.in\nbbb.in\nccc.in\n")
+test.must_not_exist(test.workpath('src', 'cat.out'))
+
+test.up_to_date(chdir = 'src', arguments = '.')
+
+test.run(chdir = 'src', arguments = '-c .')
+
+# What we do now is to change one of the files and rebuild
+test.write(['src', 'aaa.in'], "aaa.rebuild\n")
+
+# This should just rebuild aaa.out (and all)
+test.run(chdir = 'src',
+         arguments = '--cache-readonly .',
+         stdout = test.wrap_stdout("""\
+cat(["aaa.out"], ["aaa.in"])
+Retrieved `bbb.out' from cache
+Retrieved `ccc.out' from cache
+cat(["all"], ["aaa.out", "bbb.out", "ccc.out"])
+"""))
+
+test.must_match(['src', 'all'], "aaa.rebuild\nbbb.in\nccc.in\n")
+# cat.out contains only the things we built (not got from cache)
+test.must_match(['src', 'cat.out'], "aaa.out\nall\n")
+
+test.up_to_date(chdir = 'src', arguments = '.')
+
+test.run(chdir = 'src', arguments = '-c .')
+test.unlink(['src', 'cat.out'])
+
+# Verify that aaa.out contents weren't updated with the last build
+# Then clean up.
+test.run(chdir = 'src',
+         arguments = '--cache-readonly .',
+         stdout = test.wrap_stdout("""\
+cat(["aaa.out"], ["aaa.in"])
+Retrieved `bbb.out' from cache
+Retrieved `ccc.out' from cache
+cat(["all"], ["aaa.out", "bbb.out", "ccc.out"])
+"""))
+
+test.must_match(['src', 'all'], "aaa.rebuild\nbbb.in\nccc.in\n")
+test.must_match(['src', 'cat.out'], "aaa.out\nall\n")
+
+test.up_to_date(chdir = 'src', arguments = '.')
+
+test.run(chdir = 'src', arguments = '-c .')
+test.unlink(['src', 'cat.out'])
+
+# All done.
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:

File test/option/debug-multiple.py

+#!/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__"
+
+"""
+Test that --debug can take multiple options
+"""
+
+import re
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+def cat(target, source, env):
+    open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
+env = Environment(BUILDERS={'Cat':Builder(action=Action(cat))})
+env.Cat('file.out', 'file.in')
+""")
+
+test.write('file.in', "file.in\n")
+
+# Just check that object counts for some representative classes
+# show up in the output.
+
+def find_object_count(s, stdout):
+    re_string = '\d+ +\d+   %s' % re.escape(s)
+    return re.search(re_string, stdout)
+
+objects = [
+    'Action.CommandAction',
+    'Builder.BuilderBase',
+    'Environment.Base',
+    'Executor.Executor',
+    'Node.FS',
+    'Node.FS.Base',
+    'Node.Node',
+]
+
+for args in ['--debug=prepare,count', '--debug=count,prepare']:
+    test.run(arguments = args)
+    stdout = test.stdout()
+    missing = [o for o in objects if find_object_count(o, stdout) is None]
+
+    if missing:
+        print "Missing the following object lines from '%s' output:" % args
+        print "\t", ' '.join(missing)
+        print "STDOUT =========="
+        print stdout
+        test.fail_test(1)
+
+    if 'Preparing target file.out...' not in stdout:
+        print "Missing 'Preparing' lines from '%s' output:" % args
+        print "STDOUT =========="
+        print stdout
+        test.fail_test(1)
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: