Implement the Clean() function.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 27 Dec 2002 04:52:10 +0000 (04:52 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Fri, 27 Dec 2002 04:52:10 +0000 (04:52 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@529 fdb21ef1-2011-0410-befe-b5e4ea1792b1

doc/man/scons.1
src/CHANGES.txt
src/engine/SCons/Script/SConscript.py
src/engine/SCons/Script/__init__.py
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py
test/option-c.py

index 5ef46a87c47ea27077eb961fd5fa1501001d15c5..26215ad67f14754dee36471bd7a324ea71519267 100644 (file)
@@ -168,6 +168,8 @@ scons -c build export
 .EE
 
 to remove target files under build and export.
+Additional files or directories to remove can be specified using the
+Clean() function.
 
 A subset of a hierarchical tree may be built by
 remaining at the top-level directory (where the 
@@ -277,6 +279,8 @@ Ignored for compatibility with non-GNU versions of
 -c, --clean, --remove
 Clean up by removing all target files for which a construction
 command is specified.
+Also remove any files or directories associated to the construction command
+using the Clean() function.
 
 .\" .TP
 .\" --cache-disable, --no-cache
@@ -2286,6 +2290,32 @@ build).
 is usually safe, and is always more efficient than 
 .IR duplicate =1.
 
+.TP 
+.RI Clean ( target, files_or_dirs )
+This specifies a list of files or directories which should be removed
+whenever the target is specified with the
+.B -c
+command line option.
+Multiple calls to
+.BR Clean ()
+are legal,
+and create a new target or add files and directories to the
+clean list for the specified target.
+
+Multiple files or directories should be specified
+either as separate arguments to the
+.BR Clean ()
+method, or as a list.
+.BR Clean ()
+will also accept the return value of any of the construction environment
+Builder methods.
+Examples:
+
+.ES
+Clean('foo', ['bar', 'baz'])
+Clean('dist', env.Program('hello', 'hello.c'))
+.EE
+
 .TP 
 .RI Default( targets )
 This specifies a list of default targets,
index 3869feb0a61c9e3dc13ed0985654a2b7956f9b37..f71988301898440d3beacf04b8f908dfaed554a2 100644 (file)
@@ -19,6 +19,11 @@ RELEASE 0.10 - XXX
 
   - Remove Python bytecode (*.pyc) files from the scons-local packages.
 
+  From Steve Leblanc:
+
+  - Add a Clean() method to support removing user-specified targets
+    when using the -c option.
+
   From Anthony Roach:
 
   - Add SetJobs() and GetJobs() methods to allow configuration of the
index 15f116fa597a96c147a52fc870752d5f06135094..fcb08cf668f8763200db9810e42867fbc36fbee9 100644 (file)
@@ -51,6 +51,7 @@ def do_nothing(text): pass
 HelpFunction = do_nothing
 
 default_targets = []
+clean_targets = {}
 arguments = {}
 launch_dir = os.path.abspath(os.curdir)
 
@@ -315,6 +316,21 @@ def SetJobs(num):
     except ValueError, x:
         raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(num)
     
+def Clean(target, files):
+    target = str(target)
+    if not SCons.Util.is_List(files):
+        files = [files]
+    nodes = []
+    for f in files:
+        if isinstance(f, SCons.Node.Node):
+            nodes.append(f)
+        else:
+            nodes.extend(SCons.Node.arg2nodes(f, SCons.Node.FS.default_fs.Entry))
+    if clean_targets.has_key(target):
+        clean_targets[target].extend(nodes)
+    else:
+        clean_targets[target] = nodes
+
 def BuildDefaultGlobals():
     """
     Create a dictionary containing all the default globals for 
@@ -326,6 +342,7 @@ def BuildDefaultGlobals():
     globals['ARGUMENTS']         = arguments
     globals['BuildDir']          = BuildDir
     globals['Builder']           = SCons.Builder.Builder
+    globals['Clean']             = Clean
     globals['CScan']             = SCons.Defaults.CScan
     globals['Default']           = Default
     globals['Dir']               = SCons.Node.FS.default_fs.Dir
index 0068e8c898c50c96314b2bd3250af833ffe890b8..b4afb3c58c7dff7e64a465279164e6c26694c752 100644 (file)
@@ -65,6 +65,7 @@ import SCons.Builder
 import SCons.Script.SConscript
 import SCons.Warnings
 from SCons.Optik import OptionParser, SUPPRESS_HELP, OptionValueError
+from SCons.Util import display
 
 
 #
@@ -146,6 +147,10 @@ class CleanTask(SCons.Taskmaster.Task):
     def show(self):
         if self.targets[0].builder or self.targets[0].side_effect:
             display("Removed " + str(self.targets[0]))
+        if SCons.Script.SConscript.clean_targets.has_key(str(self.targets[0])):
+            files = SCons.Script.SConscript.clean_targets[str(self.targets[0])]
+            for f in files:
+                SCons.Utils.fs_delete(str(f), 0)
 
     def remove(self):
         if self.targets[0].builder or self.targets[0].side_effect:
@@ -157,6 +162,10 @@ class CleanTask(SCons.Taskmaster.Task):
                 else:
                     if removed:
                         display("Removed " + str(t))
+        if SCons.Script.SConscript.clean_targets.has_key(str(self.targets[0])):
+            files = SCons.Script.SConscript.clean_targets[str(self.targets[0])]
+            for f in files:
+                SCons.Util.fs_delete(str(f))
 
     execute = remove
 
@@ -192,11 +201,6 @@ repositories = []
 sig_module = None
 num_jobs = 1 # this is modifed by SConscript.SetJobs()
 
-def print_it(text):
-    print text
-
-display = print_it
-
 # Exceptions for this module
 class PrintHelp(Exception):
     pass
@@ -408,10 +412,8 @@ class OptParser(OptionParser):
                         "--touch", action="callback", callback=opt_ignore,
                         help="Ignored for compatibility.")
 
-        def opt_c(option, opt, value, parser):
-            setattr(parser.values, 'clean', 1)
-        self.add_option('-c', '--clean', '--remove', action="callback",
-                        callback=opt_c,
+        self.add_option('-c', '--clean', '--remove', action="store_true",
+                        default=0, dest="clean",
                         help="Remove specified targets and dependencies.")
 
         self.add_option('-C', '--directory', type="string", action = "append",
@@ -662,10 +664,7 @@ def _main():
         SCons.Node.FS.execute_actions = None
         CleanTask.execute = CleanTask.show
     if options.no_progress or options.silent:
-        global display
-        def dont_print_it(text):
-            pass
-        display = dont_print_it
+        display.set_mode(0)
     if options.silent:
         SCons.Action.print_actions = None
     if options.directory:
index 17b288271ac732f620b9d22e23199e1770a2a535..5a1c0784ac3e41013253a479dbaa46eba07fe74e 100644 (file)
@@ -326,6 +326,22 @@ class CmdStringHolder:
     def __cmp__(self, rhs):
         return cmp(self.flatdata, str(rhs))
         
+class DisplayEngine:
+    def __init__(self):
+        self.__call__ = self.print_it
+
+    def print_it(self, text):
+        print text
+
+    def dont_print(self, text):
+        pass
+
+    def set_mode(self, mode):
+        if mode:
+            self.__call__ = self.print_it
+        else:
+            self.__call__ = self.dont_print
+
 
 def scons_subst_list(strSubst, globals, locals, remove=None):
     """
@@ -722,3 +738,32 @@ def ParseConfig(env, command, function=None):
     if type(command) is type([]):
         command = string.join(command)
     return function(env, os.popen(command).read())
+
+def dir_index(directory):
+    files = []
+    for file in os.listdir(directory):
+        fullname = os.path.join(directory, file)
+        files.append(fullname)
+    return files
+
+def fs_delete(path, remove=1):
+    try:
+        if os.path.exists(path):
+            if os.path.isfile(path):
+                if remove: os.unlink(path)
+                display("Removed " + path)
+            elif os.path.isdir(path) and not os.path.islink(path):
+                # delete everything in the dir
+                for p in dir_index(path):
+                    if os.path.isfile(p):
+                        if remove: os.unlink(p)
+                        display("Removed " + p)
+                    else:
+                        fs_delete(p, remove)
+                # then delete dir itself
+                if remove: os.rmdir(path)
+                display("Removed directory " + path)
+    except OSError, e:
+        print "scons: Could not remove '%s':" % str(t), e.strerror
+
+display = DisplayEngine()
index 1f609ab9192ff455efae93f23f55102ad4b1ab15..2da792fa33262b40c1f85c5ea2ebbfd992e66fa6 100644 (file)
@@ -35,6 +35,15 @@ import SCons.Node.FS
 from SCons.Util import *
 import TestCmd
 
+
+class OutBuffer:
+    def __init__(self):
+        self.buffer = ""
+
+    def write(self, str):
+        self.buffer = self.buffer + str
+
+
 class UtilTestCase(unittest.TestCase):
     def test_subst(self):
        """Test the subst function."""
@@ -496,7 +505,48 @@ class UtilTestCase(unittest.TestCase):
         env=DummyEnv()
         res=mapPaths('bleh', dir, env)
         assert res[0] == os.path.normpath('foo/bar'), res[1]
-        
+
+    def test_display(self):
+        old_stdout = sys.stdout
+        sys.stdout = OutBuffer()
+        SCons.Util.display("line1")
+        display.set_mode(0)
+        SCons.Util.display("line2")
+        display.set_mode(1)
+        SCons.Util.display("line3")
+
+        assert sys.stdout.buffer == "line1\nline3\n"
+        sys.stdout = old_stdout
+
+    def test_fs_delete(self):
+        test = TestCmd.TestCmd(workdir = '')
+        base = test.workpath('')
+        xxx = test.workpath('xxx.xxx')
+        sub1_yyy = test.workpath('sub1', 'yyy.yyy')
+        test.subdir('sub1')
+        test.write(xxx, "\n")
+        test.write(sub1_yyy, "\n")
+
+        old_stdout = sys.stdout
+        sys.stdout = OutBuffer()
+
+        exp = "Removed " + os.path.join(base, sub1_yyy) + '\n' + \
+              "Removed directory " + os.path.join(base, 'sub1') + '\n' + \
+              "Removed " + os.path.join(base, xxx) + '\n' + \
+              "Removed directory " + base + '\n'
+
+        SCons.Util.fs_delete(base, remove=0)
+        assert sys.stdout.buffer == exp
+        assert os.path.exists(sub1_yyy)
+
+        sys.stdout.buffer = ""
+        SCons.Util.fs_delete(base, remove=1)
+        assert sys.stdout.buffer == exp
+        assert not os.path.exists(base)
+
+        test._dirlist = None
+        sys.stdout = old_stdout
+
 
 if __name__ == "__main__":
     suite = unittest.makeSuite(UtilTestCase, 'test_')
index 060ebfb1bfef7c7eddf32b06c5da446e0a83755a..5d1a7140d9d56ebb809ebcb856e82b9a0351286b 100644 (file)
@@ -127,12 +127,46 @@ test.fail_test(test.read(test.workpath('foo3.out')) != "foo3.in\n")
 
 test.writable('.', 0)
 f = open(test.workpath('foo1.out'))
-
 test.run(arguments = '-c foo1.out',
          stdout = test.wrap_stdout("scons: Could not remove 'foo1.out': Permission denied\n"))
-
 test.fail_test(not os.path.exists(test.workpath('foo1.out')))
-
 f.close()
+test.writable('.', 1)
+
+test.subdir('subd')
+test.write(['subd', 'foon.in'], "foon.in\n")
+test.write('aux1.x', "aux1.x\n")
+test.write('aux2.x', "aux2.x\n")
+test.write('SConstruct', """
+B = Builder(action = r'%s build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'foo1.out', source = 'foo1.in')
+env.B(target = 'foo2.out', source = 'foo2.xxx')
+env.B(target = 'foo2.xxx', source = 'foo2.in')
+env.B(target = 'foo3.out', source = 'foo3.in')
+Clean('foo2.xxx', ['aux1.x'])
+Clean('foo2.xxx', ['aux2.x'])
+Clean('.', ['subd'])
+""" % python)
+
+expect = test.wrap_stdout("""Removed foo2.xxx
+Removed aux1.x
+Removed aux2.x
+""")
+test.run(arguments = '-c foo2.xxx', stdout=expect)
+test.fail_test(test.read(test.workpath('foo1.out')) != "foo1.in\n")
+test.fail_test(os.path.exists(test.workpath('foo2.xxx')))
+test.fail_test(test.read(test.workpath('foo2.out')) != "foo2.in\n")
+test.fail_test(test.read(test.workpath('foo3.out')) != "foo3.in\n")
+
+expect = test.wrap_stdout("""Removed foo1.out
+Removed foo2.out
+Removed foo3.out
+Removed %s
+Removed directory subd
+""" % os.path.join('subd','foon.in'))
+test.run(arguments = '-c .', stdout=expect)
+test.fail_test(os.path.exists(test.workpath('subdir', 'foon.in')))
+test.fail_test(os.path.exists(test.workpath('subdir')))
 
 test.pass_test()