Toplevel directory cleanup
[senf.git] / tools / scons-1.2.0 / engine / SCons / Script / Interactive.py
1 #
2 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "src/engine/SCons/Script/Interactive.py 3842 2008/12/20 22:59:52 scons"
25
26 __doc__ = """
27 SCons interactive mode
28 """
29
30 # TODO:
31 #
32 # This has the potential to grow into something with a really big life
33 # of its own, which might or might not be a good thing.  Nevertheless,
34 # here are some enhancements that will probably be requested some day
35 # and are worth keeping in mind (assuming this takes off):
36
37 # - A command to re-read / re-load the SConscript files.  This may
38 #   involve allowing people to specify command-line options (e.g. -f,
39 #   -I, --no-site-dir) that affect how the SConscript files are read.
40 #
41 # - Additional command-line options on the "build" command.
42 #
43 #   Of the supported options that seemed to make sense (after a quick
44 #   pass through the list), the ones that seemed likely enough to be
45 #   used are listed in the man page and have explicit test scripts.
46 #
47 #   These had code changed in Script/Main.py to support them, but didn't
48 #   seem likely to be used regularly, so had no test scripts added:
49 #
50 #       build --diskcheck=*
51 #       build --implicit-cache=*
52 #       build --implicit-deps-changed=*
53 #       build --implicit-deps-unchanged=*
54 #
55 #   These look like they should "just work" with no changes to the
56 #   existing code, but like those above, look unlikely to be used and
57 #   therefore had no test scripts added:
58 #
59 #       build --random
60 #
61 #   These I'm not sure about.  They might be useful for individual
62 #   "build" commands, and may even work, but they seem unlikely enough
63 #   that we'll wait until they're requested before spending any time on
64 #   writing test scripts for them, or investigating whether they work.
65 #
66 #       build -q [???  is there a useful analog to the exit status?]
67 #       build --duplicate=
68 #       build --profile=
69 #       build --max-drift=
70 #       build --warn=*
71 #       build --Y
72 #
73 # - Most of the SCons command-line options that the "build" command
74 #   supports should be settable as default options that apply to all
75 #   subsequent "build" commands.  Maybe a "set {option}" command that
76 #   maps to "SetOption('{option}')".
77 #
78 # - Need something in the 'help' command that prints the -h output.
79 #
80 # - A command to run the configure subsystem separately (must see how
81 #   this interacts with the new automake model).
82 #
83 # - Command-line completion of target names; maybe even of SCons options?
84 #   Completion is something that's supported by the Python cmd module,
85 #   so this should be doable without too much trouble.
86 #
87
88 import cmd
89 import copy
90 import os
91 import re
92 import shlex
93 import string
94 import sys
95
96 try:
97     import readline
98 except ImportError:
99     pass
100
101 class SConsInteractiveCmd(cmd.Cmd):
102     """\
103     build [TARGETS]         Build the specified TARGETS and their dependencies.
104                             'b' is a synonym.
105     clean [TARGETS]         Clean (remove) the specified TARGETS and their
106                             dependencies.  'c' is a synonym.
107     exit                    Exit SCons interactive mode.
108     help [COMMAND]          Prints help for the specified COMMAND.  'h' and
109                             '?' are synonyms.
110     shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and '!'
111                             are synonyms.
112     version                 Prints SCons version information.
113     """
114
115     synonyms = {
116         'b'     : 'build',
117         'c'     : 'clean',
118         'h'     : 'help',
119         'scons' : 'build',
120         'sh'    : 'shell',
121     }
122
123     def __init__(self, **kw):
124         cmd.Cmd.__init__(self)
125         for key, val in kw.items():
126             setattr(self, key, val)
127
128         if sys.platform == 'win32':
129             self.shell_variable = 'COMSPEC'
130         else:
131             self.shell_variable = 'SHELL'
132
133     def default(self, argv):
134         print "*** Unknown command: %s" % argv[0]
135
136     def onecmd(self, line):
137         line = string.strip(line)
138         if not line:
139             print self.lastcmd
140             return self.emptyline()
141         self.lastcmd = line
142         if line[0] == '!':
143             line = 'shell ' + line[1:]
144         elif line[0] == '?':
145             line = 'help ' + line[1:]
146         if os.sep == '\\':
147             line = string.replace(line, '\\', '\\\\')
148         argv = shlex.split(line)
149         argv[0] = self.synonyms.get(argv[0], argv[0])
150         if not argv[0]:
151             return self.default(line)
152         else:
153             try:
154                 func = getattr(self, 'do_' + argv[0])
155             except AttributeError:
156                 return self.default(argv)
157             return func(argv)
158
159     def do_build(self, argv):
160         """\
161         build [TARGETS]         Build the specified TARGETS and their
162                                 dependencies.  'b' is a synonym.
163         """
164         import SCons.Node
165         import SCons.SConsign
166         import SCons.Script.Main
167
168         options = copy.deepcopy(self.options)
169
170         options, targets = self.parser.parse_args(argv[1:], values=options)
171
172         SCons.Script.COMMAND_LINE_TARGETS = targets
173
174         if targets:
175             SCons.Script.BUILD_TARGETS = targets
176         else:
177             # If the user didn't specify any targets on the command line,
178             # use the list of default targets.
179             SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default
180
181         nodes = SCons.Script.Main._build_targets(self.fs,
182                                                  options,
183                                                  targets,
184                                                  self.target_top)
185
186         if not nodes:
187             return
188
189         # Call each of the Node's alter_targets() methods, which may
190         # provide additional targets that ended up as part of the build
191         # (the canonical example being a VariantDir() when we're building
192         # from a source directory) and which we therefore need their
193         # state cleared, too.
194         x = []
195         for n in nodes:
196             x.extend(n.alter_targets()[0])
197         nodes.extend(x)
198
199         # Clean up so that we can perform the next build correctly.
200         #
201         # We do this by walking over all the children of the targets,
202         # and clearing their state.
203         #
204         # We currently have to re-scan each node to find their
205         # children, because built nodes have already been partially
206         # cleared and don't remember their children.  (In scons
207         # 0.96.1 and earlier, this wasn't the case, and we didn't
208         # have to re-scan the nodes.)
209         #
210         # Because we have to re-scan each node, we can't clear the
211         # nodes as we walk over them, because we may end up rescanning
212         # a cleared node as we scan a later node.  Therefore, only
213         # store the list of nodes that need to be cleared as we walk
214         # the tree, and clear them in a separate pass.
215         #
216         # XXX: Someone more familiar with the inner workings of scons
217         # may be able to point out a more efficient way to do this.
218
219         SCons.Script.Main.progress_display("scons: Clearing cached node information ...")
220
221         seen_nodes = {}
222
223         def get_unseen_children(node, parent, seen_nodes=seen_nodes):
224             def is_unseen(node, seen_nodes=seen_nodes):
225                 return not seen_nodes.has_key(node)
226             return filter(is_unseen, node.children(scan=1))
227
228         def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
229             seen_nodes[node] = 1
230
231             # If this file is in a VariantDir and has a
232             # corresponding source file in the source tree, remember the
233             # node in the source tree, too.  This is needed in
234             # particular to clear cached implicit dependencies on the
235             # source file, since the scanner will scan it if the
236             # VariantDir was created with duplicate=0.
237             try:
238                 rfile_method = node.rfile
239             except AttributeError:
240                 return
241             else:
242                 rfile = rfile_method()
243             if rfile != node:
244                 seen_nodes[rfile] = 1
245
246         for node in nodes:
247             walker = SCons.Node.Walker(node,
248                                         kids_func=get_unseen_children,
249                                         eval_func=add_to_seen_nodes)
250             n = walker.next()
251             while n:
252                 n = walker.next()
253
254         for node in seen_nodes.keys():
255             # Call node.clear() to clear most of the state
256             node.clear()
257             # node.clear() doesn't reset node.state, so call
258             # node.set_state() to reset it manually
259             node.set_state(SCons.Node.no_state)
260             node.implicit = None
261
262             # Debug:  Uncomment to verify that all Taskmaster reference
263             # counts have been reset to zero.
264             #if node.ref_count != 0:
265             #    from SCons.Debug import Trace
266             #    Trace('node %s, ref_count %s !!!\n' % (node, node.ref_count))
267
268         SCons.SConsign.Reset()
269         SCons.Script.Main.progress_display("scons: done clearing node information.")
270
271     def do_clean(self, argv):
272         """\
273         clean [TARGETS]         Clean (remove) the specified TARGETS
274                                 and their dependencies.  'c' is a synonym.
275         """
276         return self.do_build(['build', '--clean'] + argv[1:])
277
278     def do_EOF(self, argv):
279         print
280         self.do_exit(argv)
281
282     def _do_one_help(self, arg):
283         try:
284             # If help_<arg>() exists, then call it.
285             func = getattr(self, 'help_' + arg)
286         except AttributeError:
287             try:
288                 func = getattr(self, 'do_' + arg)
289             except AttributeError:
290                 doc = None
291             else:
292                 doc = self._doc_to_help(func)
293             if doc:
294                 sys.stdout.write(doc + '\n')
295                 sys.stdout.flush()
296         else:
297             doc = self.strip_initial_spaces(func())
298             if doc:
299                 sys.stdout.write(doc + '\n')
300                 sys.stdout.flush()
301
302     def _doc_to_help(self, obj):
303         doc = obj.__doc__
304         if doc is None:
305             return ''
306         return self._strip_initial_spaces(doc)
307
308     def _strip_initial_spaces(self, s):
309         #lines = s.split('\n')
310         lines = string.split(s, '\n')
311         spaces = re.match(' *', lines[0]).group(0)
312         #def strip_spaces(l):
313         #    if l.startswith(spaces):
314         #        l = l[len(spaces):]
315         #    return l
316         #return '\n'.join([ strip_spaces(l) for l in lines ])
317         def strip_spaces(l, spaces=spaces):
318             if l[:len(spaces)] == spaces:
319                 l = l[len(spaces):]
320             return l
321         lines = map(strip_spaces, lines)
322         return string.join(lines, '\n')
323
324     def do_exit(self, argv):
325         """\
326         exit                    Exit SCons interactive mode.
327         """
328         sys.exit(0)
329
330     def do_help(self, argv):
331         """\
332         help [COMMAND]          Prints help for the specified COMMAND.  'h'
333                                 and '?' are synonyms.
334         """
335         if argv[1:]:
336             for arg in argv[1:]:
337                 if self._do_one_help(arg):
338                     break
339         else:
340             # If bare 'help' is called, print this class's doc
341             # string (if it has one).
342             doc = self._doc_to_help(self.__class__)
343             if doc:
344                 sys.stdout.write(doc + '\n')
345                 sys.stdout.flush()
346
347     def do_shell(self, argv):
348         """\
349         shell [COMMANDLINE]     Execute COMMANDLINE in a subshell.  'sh' and
350                                 '!' are synonyms.
351         """
352         import subprocess
353         argv = argv[1:]
354         if not argv:
355             argv = os.environ[self.shell_variable]
356         try:
357             p = subprocess.Popen(argv)
358         except EnvironmentError, e:
359             sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror))
360         else:
361             p.wait()
362
363     def do_version(self, argv):
364         """\
365         version                 Prints SCons version information.
366         """
367         sys.stdout.write(self.parser.version + '\n')
368
369 def interact(fs, parser, options, targets, target_top):
370     c = SConsInteractiveCmd(prompt = 'scons>>> ',
371                             fs = fs,
372                             parser = parser,
373                             options = options,
374                             targets = targets,
375                             target_top = target_top)
376     c.cmdloop()