Merged revisions 2454-2525 via svnmerge from
[scons.git] / bin / sconsoutput.py
1 #!/usr/bin/env python2
2
3 #
4 # Copyright (c) 2003 Steven Knight
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included
15 # in all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
18 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
19 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #
25
26 __revision__ = "/home/scons/sconsoutput/branch.0/baseline/src/sconsoutput.py 0.4.D001 2004/11/27 18:44:37 knight"
27
28 #
29 # sconsoutput.py -   an SGML preprocessor for capturing SCons output
30 #                    and inserting it into examples in our DocBook
31 #                    documentation
32 #
33 # This script looks for some SGML tags that describe SCons example
34 # configurations and commands to execute in those configurations, and
35 # uses TestCmd.py to execute the commands and insert the output from
36 # those commands into the SGML that we output.  This way, we can run a
37 # script and update all of our example documentation output without
38 # a lot of laborious by-hand checking.
39 #
40 # An "SCons example" looks like this, and essentially describes a set of
41 # input files (program source files as well as SConscript files):
42 #
43 #       <scons_example name="ex1">
44 #         <file name="SConstruct" printme="1">
45 #           env = Environment()
46 #           env.Program('foo')
47 #         </file>
48 #         <file name="foo.c">
49 #           int main() { printf("foo.c\n"); }
50 #         </file>
51 #       </scons_example>
52 #
53 # The <file> contents within the <scons_example> tag will get written
54 # into a temporary directory whenever example output needs to be
55 # generated.  By default, the <file> contents are not inserted into text
56 # directly, unless you set the "printme" attribute on one or more files,
57 # in which case they will get inserted within a <programlisting> tag.
58 # This makes it easy to define the example at the appropriate
59 # point in the text where you intend to show the SConstruct file.
60 #
61 # Note that you should usually give the <scons_example> a "name"
62 # attribute so that you can refer to the example configuration later to
63 # run SCons and generate output.
64 #
65 # If you just want to show a file's contents without worry about running
66 # SCons, there's a shorter <sconstruct> tag:
67 #
68 #       <sconstruct>
69 #         env = Environment()
70 #         env.Program('foo')
71 #       </sconstruct>
72 #
73 # This is essentially equivalent to <scons_example><file printme="1">,
74 # but it's more straightforward.
75 #
76 # SCons output is generated from the following sort of tag:
77 #
78 #       <scons_output example="ex1" os="posix">
79 #         <scons_output_command>scons -Q foo</scons_output_command>
80 #         <scons_output_command>scons -Q foo</scons_output_command>
81 #       </scons_output>
82 #
83 # You tell it which example to use with the "example" attribute, and then
84 # give it a list of <scons_output_command> tags to execute.  You can also
85 # supply an "os" tag, which specifies the type of operating system this
86 # example is intended to show; if you omit this, default value is "posix".
87 #
88 # The generated SGML will show the command line (with the appropriate
89 # command-line prompt for the operating system), execute the command in
90 # a temporary directory with the example files, capture the standard
91 # output from SCons, and insert it into the text as appropriate.
92 # Error output gets passed through to your error output so you
93 # can see if there are any problems executing the command.
94 #
95
96 import os
97 import os.path
98 import re
99 import sgmllib
100 import string
101 import sys
102 import time
103
104 sys.path.append(os.path.join(os.getcwd(), 'QMTest'))
105 sys.path.append(os.path.join(os.getcwd(), 'build', 'QMTest'))
106
107 scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py')
108 if not os.path.exists(scons_py):
109     scons_py = os.path.join('src', 'script', 'scons.py')
110
111 scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine')
112 if not os.path.exists(scons_lib_dir):
113     scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine')
114
115 os.environ['SCONS_LIB_DIR'] = scons_lib_dir
116
117 import TestCmd
118
119 # The regular expression that identifies entity references in the
120 # standard sgmllib omits the underscore from the legal characters.
121 # Override it with our own regular expression that adds underscore.
122 sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]')
123
124 # Classes for collecting different types of data we're interested in.
125 class DataCollector:
126     """Generic class for collecting data between a start tag and end
127     tag.  We subclass for various types of tags we care about."""
128     def __init__(self):
129         self.data = ""
130     def afunc(self, data):
131         self.data = self.data + data
132
133 class Example(DataCollector):
134     """An SCons example.  This is essentially a list of files that
135     will get written to a temporary directory to collect output
136     from one or more SCons runs."""
137     def __init__(self):
138         DataCollector.__init__(self)
139         self.files = []
140         self.dirs = []
141
142 class File(DataCollector):
143     """A file, that will get written out to a temporary directory
144     for one or more SCons runs."""
145     def __init__(self, name):
146         DataCollector.__init__(self)
147         self.name = name
148
149 class Directory(DataCollector):
150     """A directory, that will get created in a temporary directory
151     for one or more SCons runs."""
152     def __init__(self, name):
153         DataCollector.__init__(self)
154         self.name = name
155
156 class Output(DataCollector):
157     """Where the command output goes.  This is essentially
158     a list of commands that will get executed."""
159     def __init__(self):
160         DataCollector.__init__(self)
161         self.commandlist = []
162
163 class Command(DataCollector):
164     """A tag for where the command output goes.  This is essentially
165     a list of commands that will get executed."""
166     def __init__(self):
167         DataCollector.__init__(self)
168         self.output = None
169
170 Prompt = {
171     'posix' : '% ',
172     'win32' : 'C:\\>'
173 }
174
175 # The magick SCons hackery that makes this work.
176 #
177 # So that our examples can still use the default SConstruct file, we
178 # actually feed the following into SCons via stdin and then have it
179 # SConscript() the SConstruct file.  This stdin wrapper creates a set
180 # of ToolSurrogates for the tools for the appropriate platform.  These
181 # Surrogates print output like the real tools and behave like them
182 # without actually having to be on the right platform or have the right
183 # tool installed.
184 #
185 # The upshot:  The wrapper transparently changes the world out from
186 # under the top-level SConstruct file in an example just so we can get
187 # the command output.
188
189 Stdin = """\
190 import os
191 import re
192 import string
193 import SCons.Action
194 import SCons.Defaults
195 import SCons.Node.FS
196
197 platform = '%(osname)s'
198
199 Sep = {
200     'posix' : '/',
201     'win32' : '\\\\',
202 }[platform]
203
204
205 #  Slip our own __str__() method into the EntryProxy class used to expand
206 #  $TARGET{S} and $SOURCE{S} to translate the path-name separators from
207 #  what's appropriate for the system we're running on to what's appropriate
208 #  for the example system.
209 orig = SCons.Node.FS.EntryProxy
210 class MyEntryProxy(orig):
211     def __str__(self):
212         return string.replace(str(self._Proxy__subject), os.sep, Sep)
213 SCons.Node.FS.EntryProxy = MyEntryProxy
214
215 # Slip our own RDirs() method into the Node.FS.File class so that the
216 # expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name
217 # separators translated from what's appropriate for the system we're
218 # running on to what's appropriate for the example system.
219 orig_RDirs = SCons.Node.FS.File.RDirs
220 def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs):
221     return map(lambda x: string.replace(str(x), os.sep, Sep),
222                orig_RDirs(self, pathlist))
223 SCons.Node.FS.File.RDirs = my_RDirs
224
225 class Curry:
226     def __init__(self, fun, *args, **kwargs):
227         self.fun = fun
228         self.pending = args[:]
229         self.kwargs = kwargs.copy()
230
231     def __call__(self, *args, **kwargs):
232         if kwargs and self.kwargs:
233             kw = self.kwargs.copy()
234             kw.update(kwargs)
235         else:
236             kw = kwargs or self.kwargs
237
238         return apply(self.fun, self.pending + args, kw)
239
240 def Str(target, source, env, cmd=""):
241     return env.subst(cmd, target=target, source=source)
242
243 class ToolSurrogate:
244     def __init__(self, tool, variable, func, varlist):
245         self.tool = tool
246         if not type(variable) is type([]):
247             variable = [variable]
248         self.variable = variable
249         self.func = func
250         self.varlist = varlist
251     def __call__(self, env):
252         t = Tool(self.tool)
253         t.generate(env)
254         for v in self.variable:
255             orig = env[v]
256             try:
257                 strfunction = orig.strfunction
258             except AttributeError:
259                 strfunction = Curry(Str, cmd=orig)
260             # Don't call Action() through its global function name, because
261             # that leads to infinite recursion in trying to initialize the
262             # Default Environment.
263             env[v] = SCons.Action.Action(self.func,
264                                          strfunction=strfunction,
265                                          varlist=self.varlist)
266     def __repr__(self):
267         # This is for the benefit of printing the 'TOOLS'
268         # variable through env.Dump().
269         return repr(self.tool)
270
271 def Null(target, source, env):
272     pass
273
274 def Cat(target, source, env):
275     target = str(target[0])
276     f = open(target, "wb")
277     for src in map(str, source):
278         f.write(open(src, "rb").read())
279     f.close()
280
281 def CCCom(target, source, env):
282     target = str(target[0])
283     fp = open(target, "wb")
284     def process(source_file, fp=fp):
285         for line in open(source_file, "rb").readlines():
286             m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
287             if m:
288                 include = m.group(1)
289                 for d in [str(env.Dir('$CPPPATH')), '.']:
290                     f = os.path.join(d, include)
291                     if os.path.exists(f):
292                         process(f)
293                         break
294             elif line[:11] != "STRIP CCCOM":
295                 fp.write(line)
296     for src in map(str, source):
297         process(src)
298         fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
299     fp.close()
300
301 public_class_re = re.compile('^public class (\S+)', re.MULTILINE)
302
303 def JavaCCom(target, source, env):
304     # This is a fake Java compiler that just looks for
305     #   public class FooBar
306     # lines in the source file(s) and spits those out
307     # to .class files named after the class.
308     tlist = map(str, target)
309     not_copied = {}
310     for t in tlist:
311        not_copied[t] = 1
312     for src in map(str, source):
313         contents = open(src, "rb").read()
314         classes = public_class_re.findall(contents)
315         for c in classes:
316             for t in filter(lambda x: string.find(x, c) != -1, tlist):
317                 open(t, "wb").write(contents)
318                 del not_copied[t]
319     for t in not_copied.keys():
320         open(t, "wb").write("\\n")
321
322 def JavaHCom(target, source, env):
323     tlist = map(str, target)
324     slist = map(str, source)
325     for t, s in zip(tlist, slist):
326         open(t, "wb").write(open(s, "rb").read())
327
328 def find_class_files(arg, dirname, names):
329     class_files = filter(lambda n: n[-6:] == '.class', names)
330     paths = map(lambda n, d=dirname: os.path.join(d, n), class_files)
331     arg.extend(paths)
332
333 def JarCom(target, source, env):
334     target = str(target[0])
335     class_files = []
336     for src in map(str, source):
337         os.path.walk(src, find_class_files, class_files)
338     f = open(target, "wb")
339     for cf in class_files:
340         f.write(open(cf, "rb").read())
341     f.close()
342
343 # XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand
344 # here is bogus.  It's for the benefit of doc/user/command-line.in, which
345 # uses examples that want  to rebuild based on changes to these variables.
346 # It would be better to figure out a way to do it based on the content of
347 # the generated command-line, or else find a way to let the example markup
348 # language in doc/user/command-line.in tell this script what variables to
349 # add, but that's more difficult than I want to figure out how to do right
350 # now, so let's just use the simple brute force approach for the moment.
351
352 ToolList = {
353     'posix' :   [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
354                  ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []),
355                  ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []),
356                  ('tar', 'TARCOM', Null, []),
357                  ('zip', 'ZIPCOM', Null, []),
358                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
359                  ('CVS', 'CVSCOM', Cat, []),
360                  ('RCS', 'RCS_COCOM', Cat, []),
361                  ('SCCS', 'SCCSCOM', Cat, []),
362                  ('javac', 'JAVACCOM', JavaCCom, []),
363                  ('javah', 'JAVAHCOM', JavaHCom, []),
364                  ('jar', 'JARCOM', JarCom, []),
365                  ('rmic', 'RMICCOM', Cat, []),
366                 ],
367     'win32' :   [('msvc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
368                  ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []),
369                  ('mslib', 'ARCOM', Cat, []),
370                  ('tar', 'TARCOM', Null, []),
371                  ('zip', 'ZIPCOM', Null, []),
372                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
373                  ('CVS', 'CVSCOM', Cat, []),
374                  ('RCS', 'RCS_COCOM', Cat, []),
375                  ('SCCS', 'SCCSCOM', Cat, []),
376                  ('javac', 'JAVACCOM', JavaCCom, []),
377                  ('javah', 'JAVAHCOM', JavaHCom, []),
378                  ('jar', 'JARCOM', JarCom, []),
379                  ('rmic', 'RMICCOM', Cat, []),
380                 ],
381 }
382
383 toollist = ToolList[platform]
384 filter_tools = string.split('%(tools)s')
385 if filter_tools:
386     toollist = filter(lambda x, ft=filter_tools: x[0] in ft, toollist)
387
388 toollist = map(lambda t: apply(ToolSurrogate, t), toollist)
389
390 toollist.append('install')
391
392 def surrogate_spawn(sh, escape, cmd, args, env):
393     pass
394
395 def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr):
396     pass
397
398 SCons.Defaults.ConstructionEnvironment.update({
399     'PLATFORM' : platform,
400     'TOOLS'    : toollist,
401     'SPAWN'    : surrogate_spawn,
402     'PSPAWN'   : surrogate_pspawn,
403 })
404
405 SConscript('SConstruct')
406 """
407
408 # "Commands" that we will execute in our examples.
409 def command_scons(args, c, test, dict):
410     save_vals = {}
411     delete_keys = []
412     try:
413         ce = c.environment
414     except AttributeError:
415         pass
416     else:
417         for arg in string.split(c.environment):
418             key, val = string.split(arg, '=')
419             try:
420                 save_vals[key] = os.environ[key]
421             except KeyError:
422                 delete_keys.append(key)
423             os.environ[key] = val
424     test.run(interpreter = sys.executable,
425              program = scons_py,
426              arguments = '-f - ' + string.join(args),
427              chdir = test.workpath('WORK'),
428              stdin = Stdin % dict)
429     os.environ.update(save_vals)
430     for key in delete_keys:
431         del(os.environ[key])
432     out = test.stdout()
433     out = string.replace(out, test.workpath('ROOT'), '')
434     out = string.replace(out, test.workpath('WORK/SConstruct'),
435                               '/home/my/project/SConstruct')
436     lines = string.split(out, '\n')
437     if lines:
438         while lines[-1] == '':
439             lines = lines[:-1]
440     #err = test.stderr()
441     #if err:
442     #    sys.stderr.write(err)
443     return lines
444
445 def command_touch(args, c, test, dict):
446     if args[0] == '-t':
447         t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
448         times = (t, t)
449         args = args[2:]
450     else:
451         time.sleep(1)
452         times = None
453     for file in args:
454         if not os.path.isabs(file):
455             file = os.path.join(test.workpath('WORK'), file)
456         if not os.path.exists(file):
457             open(file, 'wb')
458         os.utime(file, times)
459     return []
460
461 def command_edit(args, c, test, dict):
462     try:
463         add_string = c.edit[:]
464     except AttributeError:
465         add_string = 'void edit(void) { ; }\n'
466     if add_string[-1] != '\n':
467         add_string = add_string + '\n'
468     for file in args:
469         if not os.path.isabs(file):
470             file = os.path.join(test.workpath('WORK'), file)
471         contents = open(file, 'rb').read()
472         open(file, 'wb').write(contents + add_string)
473     return []
474
475 def command_ls(args, c, test, dict):
476     def ls(a):
477         files = os.listdir(a)
478         files = filter(lambda x: x[0] != '.', files)
479         files.sort()
480         return [string.join(files, '  ')]
481     if args:
482         l = []
483         for a in args:
484             l.extend(ls(test.workpath('WORK', a)))
485         return l
486     else:
487         return ls(test.workpath('WORK'))
488
489 CommandDict = {
490     'scons' : command_scons,
491     'touch' : command_touch,
492     'edit'  : command_edit,
493     'ls'    : command_ls,
494 }
495
496 def ExecuteCommand(args, c, t, dict):
497     try:
498         func = CommandDict[args[0]]
499     except KeyError:
500         func = lambda args, c, t, dict: []
501     return func(args[1:], c, t, dict)
502
503 class MySGML(sgmllib.SGMLParser):
504     """A subclass of the standard Python 2.2 sgmllib SGML parser.
505
506     This extends the standard sgmllib parser to recognize, and do cool
507     stuff with, the added tags that describe our SCons examples,
508     commands, and other stuff.
509
510     Note that this doesn't work with the 1.5.2 sgmllib module, because
511     that didn't have the ability to work with ENTITY declarations.
512     """
513     def __init__(self):
514         sgmllib.SGMLParser.__init__(self)
515         self.examples = {}
516         self.afunclist = []
517
518     # The first set of methods here essentially implement pass-through
519     # handling of most of the stuff in an SGML file.  We're really
520     # only concerned with the tags specific to SCons example processing,
521     # the methods for which get defined below.
522
523     def handle_data(self, data):
524         try:
525             f = self.afunclist[-1]
526         except IndexError:
527             sys.stdout.write(data)
528         else:
529             f(data)
530
531     def handle_comment(self, data):
532         sys.stdout.write('<!--' + data + '-->')
533
534     def handle_decl(self, data):
535         sys.stdout.write('<!' + data + '>')
536
537     def unknown_starttag(self, tag, attrs):
538         try:
539             f = self.example.afunc
540         except AttributeError:
541             f = sys.stdout.write
542         if not attrs:
543             f('<' + tag + '>')
544         else:
545             f('<' + tag)
546             for name, value in attrs:
547                 f(' ' + name + '=' + '"' + value + '"')
548             f('>')
549
550     def unknown_endtag(self, tag):
551         sys.stdout.write('</' + tag + '>')
552
553     def unknown_entityref(self, ref):
554         sys.stdout.write('&' + ref + ';')
555
556     def unknown_charref(self, ref):
557         sys.stdout.write('&#' + ref + ';')
558
559     # Here is where the heavy lifting begins.  The following methods
560     # handle the begin-end tags of our SCons examples.
561
562     def start_scons_example(self, attrs):
563         t = filter(lambda t: t[0] == 'name', attrs)
564         if t:
565             name = t[0][1]
566             try:
567                e = self.examples[name]
568             except KeyError:
569                e = self.examples[name] = Example()
570         else:
571             e = Example()
572         for name, value in attrs:
573             setattr(e, name, value)
574         self.e = e
575         self.afunclist.append(e.afunc)
576
577     def end_scons_example(self):
578         e = self.e
579         files = filter(lambda f: f.printme, e.files)
580         if files:
581             sys.stdout.write('<programlisting>')
582             for f in files:
583                 if f.printme:
584                     i = len(f.data) - 1
585                     while f.data[i] == ' ':
586                         i = i - 1
587                     output = string.replace(f.data[:i+1], '__ROOT__', '')
588                     output = string.replace(output, '<', '&lt;')
589                     output = string.replace(output, '>', '&gt;')
590                     sys.stdout.write(output)
591             if e.data and e.data[0] == '\n':
592                 e.data = e.data[1:]
593             sys.stdout.write(e.data + '</programlisting>')
594         delattr(self, 'e')
595         self.afunclist = self.afunclist[:-1]
596
597     def start_file(self, attrs):
598         try:
599             e = self.e
600         except AttributeError:
601             self.error("<file> tag outside of <scons_example>")
602         t = filter(lambda t: t[0] == 'name', attrs)
603         if not t:
604             self.error("no <file> name attribute found")
605         try:
606             e.prefix
607         except AttributeError:
608             e.prefix = e.data
609             e.data = ""
610         f = File(t[0][1])
611         f.printme = None
612         for name, value in attrs:
613             setattr(f, name, value)
614         e.files.append(f)
615         self.afunclist.append(f.afunc)
616
617     def end_file(self):
618         self.e.data = ""
619         self.afunclist = self.afunclist[:-1]
620
621     def start_directory(self, attrs):
622         try:
623             e = self.e
624         except AttributeError:
625             self.error("<directory> tag outside of <scons_example>")
626         t = filter(lambda t: t[0] == 'name', attrs)
627         if not t:
628             self.error("no <directory> name attribute found")
629         try:
630             e.prefix
631         except AttributeError:
632             e.prefix = e.data
633             e.data = ""
634         d = Directory(t[0][1])
635         for name, value in attrs:
636             setattr(d, name, value)
637         e.dirs.append(d)
638         self.afunclist.append(d.afunc)
639
640     def end_directory(self):
641         self.e.data = ""
642         self.afunclist = self.afunclist[:-1]
643
644     def start_scons_example_file(self, attrs):
645         t = filter(lambda t: t[0] == 'example', attrs)
646         if not t:
647             self.error("no <scons_example_file> example attribute found")
648         exname = t[0][1]
649         try:
650             e = self.examples[exname]
651         except KeyError:
652             self.error("unknown example name '%s'" % exname)
653         fattrs = filter(lambda t: t[0] == 'name', attrs)
654         if not fattrs:
655             self.error("no <scons_example_file> name attribute found")
656         fname = fattrs[0][1]
657         f = filter(lambda f, fname=fname: f.name == fname, e.files)
658         if not f:
659             self.error("example '%s' does not have a file named '%s'" % (exname, fname))
660         self.f = f[0]
661
662     def end_scons_example_file(self):
663         f = self.f
664         sys.stdout.write('<programlisting>')
665         sys.stdout.write(f.data + '</programlisting>')
666         delattr(self, 'f')
667
668     def start_scons_output(self, attrs):
669         t = filter(lambda t: t[0] == 'example', attrs)
670         if not t:
671             self.error("no <scons_output> example attribute found")
672         exname = t[0][1]
673         try:
674             e = self.examples[exname]
675         except KeyError:
676             self.error("unknown example name '%s'" % exname)
677         # Default values for an example.
678         o = Output()
679         o.preserve = None
680         o.os = 'posix'
681         o.tools = ''
682         o.e = e
683         # Locally-set.
684         for name, value in attrs:
685             setattr(o, name, value)
686         self.o = o
687         self.afunclist.append(o.afunc)
688
689     def end_scons_output(self):
690         # The real raison d'etre for this script, this is where we
691         # actually execute SCons to fetch the output.
692         o = self.o
693         e = o.e
694         t = TestCmd.TestCmd(workdir='', combine=1)
695         if o.preserve:
696             t.preserve()
697         t.subdir('ROOT', 'WORK')
698         t.rootpath = string.replace(t.workpath('ROOT'), '\\', '\\\\')
699
700         for d in e.dirs:
701             dir = t.workpath('WORK', d.name)
702             if not os.path.exists(dir):
703                 os.makedirs(dir)
704
705         for f in e.files:
706             i = 0
707             while f.data[i] == '\n':
708                 i = i + 1
709             lines = string.split(f.data[i:], '\n')
710             i = 0
711             while lines[0][i] == ' ':
712                 i = i + 1
713             lines = map(lambda l, i=i: l[i:], lines)
714             path = string.replace(f.name, '__ROOT__', t.rootpath)
715             if not os.path.isabs(path):
716                 path = t.workpath('WORK', path)
717             dir, name = os.path.split(path)
718             if dir and not os.path.exists(dir):
719                 os.makedirs(dir)
720             content = string.join(lines, '\n')
721             content = string.replace(content, '__ROOT__', t.rootpath)
722             path = t.workpath('WORK', path)
723             t.write(path, content)
724             if hasattr(f, 'chmod'):
725                 os.chmod(path, int(f.chmod, 0))
726
727         i = len(o.prefix)
728         while o.prefix[i-1] != '\n':
729             i = i - 1
730
731         sys.stdout.write('<screen>' + o.prefix[:i])
732         p = o.prefix[i:]
733
734         for c in o.commandlist:
735             sys.stdout.write(p + Prompt[o.os])
736             d = string.replace(c.data, '__ROOT__', '')
737             sys.stdout.write('<userinput>' + d + '</userinput>\n')
738
739             e = string.replace(c.data, '__ROOT__', t.workpath('ROOT'))
740             args = string.split(e)
741             lines = ExecuteCommand(args, c, t, {'osname':o.os, 'tools':o.tools})
742             content = None
743             if c.output:
744                 content = c.output
745             elif lines:
746                 content = string.join(lines, '\n' + p)
747             if content:
748                 content = re.sub(' at 0x[0-9a-fA-F]*\>', ' at 0x700000&gt;', content)
749                 content = string.replace(content, '<', '&lt;')
750                 content = string.replace(content, '>', '&gt;')
751                 sys.stdout.write(p + content + '\n')
752
753         if o.data[0] == '\n':
754             o.data = o.data[1:]
755         sys.stdout.write(o.data + '</screen>')
756         delattr(self, 'o')
757         self.afunclist = self.afunclist[:-1]
758
759     def start_scons_output_command(self, attrs):
760         try:
761             o = self.o
762         except AttributeError:
763             self.error("<scons_output_command> tag outside of <scons_output>")
764         try:
765             o.prefix
766         except AttributeError:
767             o.prefix = o.data
768             o.data = ""
769         c = Command()
770         for name, value in attrs:
771             setattr(c, name, value)
772         o.commandlist.append(c)
773         self.afunclist.append(c.afunc)
774
775     def end_scons_output_command(self):
776         self.o.data = ""
777         self.afunclist = self.afunclist[:-1]
778
779     def start_sconstruct(self, attrs):
780         f = File('')
781         self.f = f
782         self.afunclist.append(f.afunc)
783
784     def end_sconstruct(self):
785         f = self.f
786         sys.stdout.write('<programlisting>')
787         output = string.replace(f.data, '__ROOT__', '')
788         sys.stdout.write(output + '</programlisting>')
789         delattr(self, 'f')
790         self.afunclist = self.afunclist[:-1]
791
792 # The main portion of the program itself, short and simple.
793 try:
794     file = sys.argv[1]
795 except IndexError:
796     file = '-'
797
798 if file == '-':
799     f = sys.stdin
800 else:
801     try:
802         f = open(file, 'r')
803     except IOError, msg:
804         print file, ":", msg
805         sys.exit(1)
806
807 data = f.read()
808 if f is not sys.stdin:
809     f.close()
810
811 if data.startswith('<?xml '):
812     first_line, data = data.split('\n', 1)
813     sys.stdout.write(first_line + '\n')
814
815 x = MySGML()
816 for c in data:
817     x.feed(c)
818 x.close()