3 # scons_examples.py - an SGML preprocessor for capturing SCons output
4 # and inserting into examples in our DocBook
8 # This script looks for some SGML tags that describe SCons example
9 # configurations and commands to execute in those configurations, and
10 # uses TestCmd.py to execute the commands and insert the output into
11 # the output SGML. This way, we can run a script and update all of
12 # our example output without having to do a lot of laborious by-hand
15 # An "SCons example" looks like this, and essentially describes a set of
16 # input files (program source files as well as SConscript files):
18 # <scons_example name="ex1">
19 # <file name="SConstruct" printme="1">
24 # int main() { printf("foo.c\n"); }
28 # The <file> contents within the <scons_example> tag will get written
29 # into a temporary directory whenever example output needs to be
30 # generated. By default, the <file> contents are not inserted into text
31 # directly, unless you set the "printme" attribute on one or more files,
32 # in which case they will get inserted within a <programlisting> tag.
33 # This makes it easy to define the example at the appropriate
34 # point in the text where you intend to show the SConstruct file.
36 # Note that you should usually give the <scons_example> a "name"
37 # attribute so that you can refer to the example configuration later to
38 # run SCons and generate output.
40 # If you just want to show a file's contents without worry about running
41 # SCons, there's a shorter <sconstruct> tag:
48 # This is essentially equivalent to <scons_example><file printme="1">,
49 # but it's more straightforward.
51 # SCons output is generated from the following sort of tag:
53 # <scons_output example="ex1" os="posix">
54 # <command>scons -Q foo</command>
55 # <command>scons -Q foo</command>
58 # You tell it which example to use with the "example" attribute, and
59 # then give it a list of <command> tags to execute. You can also supply
60 # an "os" tag, which specifies the type of operating system this example
61 # is intended to show; if you omit this, default value is "posix".
63 # The generated SGML will show the command line (with the appropriate
64 # command-line prompt for the operating system), execute the command in
65 # a temporary directory with the example files, capture the standard
66 # output from SCons, and insert it into the text as appropriate.
67 # Error output gets passed through to your error output so you
68 # can see if there are any problems executing the command.
78 sys.path.append(os.path.join(os.getcwd(), 'etc'))
79 sys.path.append(os.path.join(os.getcwd(), 'build', 'etc'))
81 scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py')
82 if not os.path.exists(scons_py):
83 scons_py = os.path.join('src', 'script', 'scons.py')
85 scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine')
86 if not os.path.exists(scons_lib_dir):
87 scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine')
91 # The regular expression that identifies entity references in the
92 # standard sgmllib omits the underscore from the legal characters.
93 # Override it with our own regular expression that adds underscore.
94 sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]')
97 """Generic class for collecting data between a start tag and end
98 tag. We subclass for various types of tags we care about."""
101 def afunc(self, data):
102 self.data = self.data + data
104 class Example(DataCollector):
105 """An SCons example. This is essentially a list of files that
106 will get written to a temporary directory to collect output
107 from one or more SCons runs."""
109 DataCollector.__init__(self)
113 class File(DataCollector):
114 """A file, that will get written out to a temporary directory
115 for one or more SCons runs."""
116 def __init__(self, name):
117 DataCollector.__init__(self)
120 class Directory(DataCollector):
121 """A directory, that will get created in a temporary directory
122 for one or more SCons runs."""
123 def __init__(self, name):
124 DataCollector.__init__(self)
127 class Output(DataCollector):
128 """Where the command output goes. This is essentially
129 a list of commands that will get executed."""
131 DataCollector.__init__(self)
132 self.commandlist = []
134 class Command(DataCollector):
135 """A tag for where the command output goes. This is essentially
136 a list of commands that will get executed."""
144 # Magick SCons hackery.
146 # So that our examples can still use the default SConstruct file, we
147 # actually feed the following into SCons via stdin and then have it
148 # SConscript() the SConstruct file. This stdin wrapper creates a set
149 # of ToolSurrogates for the tools for the appropriate platform. These
150 # Surrogates print output like the real tools and behave like them
151 # without actually having to be on the right platform or have the right
154 # The upshot: We transparently change the world out from under the
155 # top-level SConstruct file in an example just so we can get the
159 import SCons.Defaults
164 def __init__(self, fun, *args, **kwargs):
166 self.pending = args[:]
167 self.kwargs = kwargs.copy()
169 def __call__(self, *args, **kwargs):
170 if kwargs and self.kwargs:
171 kw = self.kwargs.copy()
174 kw = kwargs or self.kwargs
176 return apply(self.fun, self.pending + args, kw)
178 def Str(target, source, env, cmd=""):
179 return env.subst(cmd, target=target, source=source)
182 def __init__(self, tool, variable, func):
184 self.variable = variable
186 def __call__(self, env):
189 orig = env[self.variable]
190 env[self.variable] = Action(self.func, strfunction=Curry(Str, cmd=orig))
192 def Null(target, source, env):
195 def Cat(target, source, env):
196 target = str(target[0])
197 f = open(target, "wb")
198 for src in map(str, source):
199 f.write(open(src, "rb").read())
203 'posix' : [('cc', 'CCCOM', Cat),
204 ('link', 'LINKCOM', Cat),
205 ('tar', 'TARCOM', Null),
206 ('zip', 'ZIPCOM', Null)],
207 'win32' : [('msvc', 'CCCOM', Cat),
208 ('mslink', 'LINKCOM', Cat)]
211 tools = map(lambda t: apply(ToolSurrogate, t), ToolList[platform])
213 SCons.Defaults.ConstructionEnvironment.update({
214 'PLATFORM' : platform,
218 SConscript('SConstruct')
221 class MySGML(sgmllib.SGMLParser):
222 """A subclass of the standard Python 2.2 sgmllib SGML parser.
224 Note that this doesn't work with the 1.5.2 sgmllib module, because
225 that didn't have the ability to work with ENTITY declarations.
228 sgmllib.SGMLParser.__init__(self)
232 def handle_data(self, data):
234 f = self.afunclist[-1]
236 sys.stdout.write(data)
240 def handle_comment(self, data):
241 sys.stdout.write('<!--' + data + '-->')
243 def handle_decl(self, data):
244 sys.stdout.write('<!' + data + '>')
246 def unknown_starttag(self, tag, attrs):
248 f = self.example.afunc
249 except AttributeError:
255 for name, value in attrs:
256 f(' ' + name + '=' + '"' + value + '"')
259 def unknown_endtag(self, tag):
260 sys.stdout.write('</' + tag + '>')
262 def unknown_entityref(self, ref):
263 sys.stdout.write('&' + ref + ';')
265 def unknown_charref(self, ref):
266 sys.stdout.write('&#' + ref + ';')
268 def start_scons_example(self, attrs):
269 t = filter(lambda t: t[0] == 'name', attrs)
273 e = self.examples[name]
275 e = self.examples[name] = Example()
278 for name, value in attrs:
279 setattr(e, name, value)
281 self.afunclist.append(e.afunc)
283 def end_scons_example(self):
285 files = filter(lambda f: f.printme, e.files)
287 sys.stdout.write('<programlisting>')
291 while f.data[i] == ' ':
293 output = string.replace(f.data[:i+1], '__ROOT__', '')
294 sys.stdout.write(output)
295 if e.data and e.data[0] == '\n':
297 sys.stdout.write(e.data + '</programlisting>')
299 self.afunclist = self.afunclist[:-1]
301 def start_file(self, attrs):
304 except AttributeError:
305 self.error("<file> tag outside of <scons_example>")
306 t = filter(lambda t: t[0] == 'name', attrs)
308 self.error("no <file> name attribute found")
311 except AttributeError:
316 for name, value in attrs:
317 setattr(f, name, value)
319 self.afunclist.append(f.afunc)
323 self.afunclist = self.afunclist[:-1]
325 def start_directory(self, attrs):
328 except AttributeError:
329 self.error("<directory> tag outside of <scons_example>")
330 t = filter(lambda t: t[0] == 'name', attrs)
332 self.error("no <directory> name attribute found")
335 except AttributeError:
338 d = Directory(t[0][1])
339 for name, value in attrs:
340 setattr(d, name, value)
342 self.afunclist.append(d.afunc)
344 def end_directory(self):
346 self.afunclist = self.afunclist[:-1]
348 def start_scons_example_file(self, attrs):
349 t = filter(lambda t: t[0] == 'example', attrs)
351 self.error("no <scons_example_file> example attribute found")
354 e = self.examples[exname]
356 self.error("unknown example name '%s'" % exname)
357 fattrs = filter(lambda t: t[0] == 'name', attrs)
359 self.error("no <scons_example_file> name attribute found")
361 f = filter(lambda f, fname=fname: f.name == fname, e.files)
363 self.error("example '%s' does not have a file named '%s'" % (exname, fname))
366 def end_scons_example_file(self):
368 sys.stdout.write('<programlisting>')
370 while f.data[i] == ' ':
372 sys.stdout.write(f.data[:i+1] + '</programlisting>')
375 def start_scons_output(self, attrs):
376 t = filter(lambda t: t[0] == 'example', attrs)
378 self.error("no <scons_output> example attribute found")
381 e = self.examples[exname]
383 self.error("unknown example name '%s'" % exname)
384 # Default values for an example.
389 for name, value in attrs:
390 setattr(o, name, value)
392 self.afunclist.append(o.afunc)
394 def end_scons_output(self):
397 t = TestCmd.TestCmd(workdir='', combine=1)
398 t.subdir('ROOT', 'WORK')
400 dir = t.workpath('WORK', d.name)
401 if not os.path.exists(dir):
405 while f.data[i] == '\n':
407 lines = string.split(f.data[i:], '\n')
409 while lines[0][i] == ' ':
411 lines = map(lambda l, i=i: l[i:], lines)
412 path = string.replace(f.name, '__ROOT__', t.workpath('ROOT'))
413 dir, name = os.path.split(f.name)
415 dir = t.workpath('WORK', dir)
416 if not os.path.exists(dir):
418 content = string.join(lines, '\n')
419 content = string.replace(content,
422 t.write(t.workpath('WORK', f.name), content)
424 while o.prefix[i-1] != '\n':
426 sys.stdout.write('<literallayout>' + o.prefix[:i])
428 for c in o.commandlist:
429 sys.stdout.write(p + Prompt[o.os])
430 d = string.replace(c.data, '__ROOT__', '')
431 sys.stdout.write('<userinput>' + d + '</userinput>\n')
432 e = string.replace(c.data, '__ROOT__', t.workpath('ROOT'))
433 args = string.split(e)[1:]
434 os.environ['SCONS_LIB_DIR'] = scons_lib_dir
435 t.run(interpreter = sys.executable,
437 arguments = '-f - ' + string.join(args),
438 chdir = t.workpath('WORK'),
439 stdin = Stdin % o.os)
440 out = string.replace(t.stdout(), t.workpath('ROOT'), '')
442 lines = string.split(out, '\n')
444 while lines[-1] == '':
447 sys.stdout.write(p + l + '\n')
450 # sys.stderr.write(err)
451 if o.data[0] == '\n':
453 sys.stdout.write(o.data + '</literallayout>')
455 self.afunclist = self.afunclist[:-1]
457 def start_command(self, attrs):
460 except AttributeError:
461 self.error("<command> tag outside of <scons_output>")
464 except AttributeError:
468 o.commandlist.append(c)
469 self.afunclist.append(c.afunc)
471 def end_command(self):
473 self.afunclist = self.afunclist[:-1]
475 def start_sconstruct(self, attrs):
476 sys.stdout.write('<programlisting>')
478 def end_sconstruct(self):
479 sys.stdout.write('</programlisting>')
496 if f is not sys.stdin: