From: stevenknight Date: Fri, 27 Dec 2002 04:52:10 +0000 (+0000) Subject: Implement the Clean() function. X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=cc19c66376ce07d63192b9dee0acde6a2d1d417d;p=scons.git Implement the Clean() function. git-svn-id: http://scons.tigris.org/svn/scons/trunk@529 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 5ef46a87..26215ad6 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -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, diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 3869feb0..f7198830 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -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 diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index 15f116fa..fcb08cf6 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -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 diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 0068e8c8..b4afb3c5 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -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: diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 17b28827..5a1c0784 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -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() diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 1f609ab9..2da792fa 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -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_') diff --git a/test/option-c.py b/test/option-c.py index 060ebfb1..5d1a7140 100644 --- a/test/option-c.py +++ b/test/option-c.py @@ -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()