summaryrefslogtreecommitdiffstats
path: root/bin/sconsexamples.py
blob: ac8dce3a906357b11a57ee553ab31135aa189902 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
#!/usr/bin/env python2
#
# scons_examples.py -   an SGML preprocessor for capturing SCons output
#                       and inserting into examples in our DocBook
#                       documentation
#

# This script looks for some SGML tags that describe SCons example
# configurations and commands to execute in those configurations, and
# uses TestCmd.py to execute the commands and insert the output into
# the output SGML.  This way, we can run a script and update all of
# our example output without having to do a lot of laborious by-hand
# checking.
#
# An "SCons example" looks like this, and essentially describes a set of
# input files (program source files as well as SConscript files):
#
#       <scons_example name="ex1">
#         <file name="SConstruct" printme="1">
#           env = Environment()
#           env.Program('foo')
#         </file>
#         <file name="foo.c">
#           int main() { printf("foo.c\n"); }
#         </file>
#       </scons_example>
#
# The <file> contents within the <scons_example> tag will get written
# into a temporary directory whenever example output needs to be
# generated.  By default, the <file> contents are not inserted into text
# directly, unless you set the "printme" attribute on one or more files,
# in which case they will get inserted within a <programlisting> tag.
# This makes it easy to define the example at the appropriate
# point in the text where you intend to show the SConstruct file.
#
# Note that you should usually give the <scons_example> a "name"
# attribute so that you can refer to the example configuration later to
# run SCons and generate output.
#
# If you just want to show a file's contents without worry about running
# SCons, there's a shorter <sconstruct> tag:
#
#       <sconstruct>
#         env = Environment()
#         env.Program('foo')
#       </sconstruct>
#
# This is essentially equivalent to <scons_example><file printme="1">,
# but it's more straightforward.
#
# SCons output is generated from the following sort of tag:
#
#       <scons_output example="ex1" os="posix">
#         <command>scons -Q foo</command>
#         <command>scons -Q foo</command>
#       </scons_output>
#
# You tell it which example to use with the "example" attribute, and
# then give it a list of <command> tags to execute.  You can also supply
# an "os" tag, which specifies the type of operating system this example
# is intended to show; if you omit this, default value is "posix".
#
# The generated SGML will show the command line (with the appropriate
# command-line prompt for the operating system), execute the command in
# a temporary directory with the example files, capture the standard
# output from SCons, and insert it into the text as appropriate.
# Error output gets passed through to your error output so you
# can see if there are any problems executing the command.
#

import os
import os.path
import re
import sgmllib
import string
import sys

sys.path.append(os.path.join(os.getcwd(), 'etc'))
sys.path.append(os.path.join(os.getcwd(), 'build', 'etc'))

scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py')
if not os.path.exists(scons_py):
    scons_py = os.path.join('src', 'script', 'scons.py')

scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine')
if not os.path.exists(scons_lib_dir):
    scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine')

import TestCmd

# The regular expression that identifies entity references in the
# standard sgmllib omits the underscore from the legal characters.
# Override it with our own regular expression that adds underscore.
sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]')

class DataCollector:
    """Generic class for collecting data between a start tag and end
    tag.  We subclass for various types of tags we care about."""
    def __init__(self):
        self.data = ""
    def afunc(self, data):
        self.data = self.data + data

class Example(DataCollector):
    """An SCons example.  This is essentially a list of files that
    will get written to a temporary directory to collect output
    from one or more SCons runs."""
    def __init__(self):
        DataCollector.__init__(self)
        self.files = []
        self.dirs = []

class File(DataCollector):
    """A file, that will get written out to a temporary directory
    for one or more SCons runs."""
    def __init__(self, name):
        DataCollector.__init__(self)
        self.name = name

class Directory(DataCollector):
    """A directory, that will get created in a temporary directory
    for one or more SCons runs."""
    def __init__(self, name):
        DataCollector.__init__(self)
        self.name = name

class Output(DataCollector):
    """Where the command output goes.  This is essentially
    a list of commands that will get executed."""
    def __init__(self):
        DataCollector.__init__(self)
        self.commandlist = []

class Command(DataCollector):
    """A tag for where the command output goes.  This is essentially
    a list of commands that will get executed."""
    pass

Prompt = {
    'posix' : '% ',
    'win32' : 'C:\\>'
}

# Magick SCons hackery.
#
# So that our examples can still use the default SConstruct file, we
# actually feed the following into SCons via stdin and then have it
# SConscript() the SConstruct file.  This stdin wrapper creates a set
# of ToolSurrogates for the tools for the appropriate platform.  These
# Surrogates print output like the real tools and behave like them
# without actually having to be on the right platform or have the right
# tool installed.
#
# The upshot:  We transparently change the world out from under the
# top-level SConstruct file in an example just so we can get the
# command output.

Stdin = """\
import string
import SCons.Defaults

platform = '%s'

class Curry:
    def __init__(self, fun, *args, **kwargs):
        self.fun = fun
        self.pending = args[:]
        self.kwargs = kwargs.copy()

    def __call__(self, *args, **kwargs):
        if kwargs and self.kwargs:
            kw = self.kwargs.copy()
            kw.update(kwargs)
        else:
            kw = kwargs or self.kwargs

        return apply(self.fun, self.pending + args, kw)

def Str(target, source, env, cmd=""):
    result = []
    for cmd in env.subst_list(cmd, target=target, source=source):
        result.append(string.join(map(str, cmd)))
    return string.join(result, '\\n')

class ToolSurrogate:
    def __init__(self, tool, variable, func):
        self.tool = tool
        self.variable = variable
        self.func = func
    def __call__(self, env):
        t = Tool(self.tool)
        t.generate(env)
        orig = env[self.variable]
        env[self.variable] = Action(self.func, strfunction=Curry(Str, cmd=orig))

def Null(target, source, env):
    pass

def Cat(target, source, env):
    target = str(target[0])
    f = open(target, "wb")
    for src in map(str, source):
        f.write(open(src, "rb").read())
    f.close()

ToolList = {
    'posix' :   [('cc', 'CCCOM', Cat),
                 ('link', 'LINKCOM', Cat),
                 ('tar', 'TARCOM', Null),
                 ('zip', 'ZIPCOM', Null)],
    'win32' :   [('msvc', 'CCCOM', Cat),
                 ('mslink', 'LINKCOM', Cat)]
}

tools = map(lambda t: apply(ToolSurrogate, t), ToolList[platform])

SCons.Defaults.ConstructionEnvironment.update({
    'PLATFORM' : platform,
    'TOOLS'    : tools,
})

SConscript('SConstruct')
"""

class MySGML(sgmllib.SGMLParser):
    """A subclass of the standard Python 2.2 sgmllib SGML parser.

    Note that this doesn't work with the 1.5.2 sgmllib module, because
    that didn't have the ability to work with ENTITY declarations.
    """
    def __init__(self):
        sgmllib.SGMLParser.__init__(self)
        self.examples = {}
        self.afunclist = []

    def handle_data(self, data):
        try:
            f = self.afunclist[-1]
        except IndexError:
            sys.stdout.write(data)
        else:
            f(data)

    def handle_comment(self, data):
        sys.stdout.write('<!--' + data + '-->')

    def handle_decl(self, data):
        sys.stdout.write('<!' + data + '>')

    def unknown_starttag(self, tag, attrs):
        try:
            f = self.example.afunc
        except AttributeError:
            f = sys.stdout.write
        if not attrs:
            f('<' + tag + '>')
        else:
            f('<' + tag)
            for name, value in attrs:
                f(' ' + name + '=' + '"' + value + '"')
            f('>')

    def unknown_endtag(self, tag):
        sys.stdout.write('</' + tag + '>')

    def unknown_entityref(self, ref):
        sys.stdout.write('&' + ref + ';')

    def unknown_charref(self, ref):
        sys.stdout.write('&#' + ref + ';')

    def start_scons_example(self, attrs):
        t = filter(lambda t: t[0] == 'name', attrs)
        if t:
            name = t[0][1]
            try:
               e = self.examples[name]
            except KeyError:
               e = self.examples[name] = Example()
        else:
            e = Example()
        for name, value in attrs:
            setattr(e, name, value)
        self.e = e
        self.afunclist.append(e.afunc)

    def end_scons_example(self):
        e = self.e
        files = filter(lambda f: f.printme, e.files)
        if files:
            sys.stdout.write('<programlisting>')
            for f in files:
                if f.printme:
                    i = len(f.data) - 1
                    while f.data[i] == ' ':
                        i = i - 1
                    output = string.replace(f.data[:i+1], '__ROOT__', '')
                    sys.stdout.write(output)
            if e.data and e.data[0] == '\n':
                e.data = e.data[1:]
            sys.stdout.write(e.data + '</programlisting>')
        delattr(self, 'e')
        self.afunclist = self.afunclist[:-1]

    def start_file(self, attrs):
        try:
            e = self.e
        except AttributeError:
            self.error("<file> tag outside of <scons_example>")
        t = filter(lambda t: t[0] == 'name', attrs)
        if not t:
            self.error("no <file> name attribute found")
        try:
            e.prefix
        except AttributeError:
            e.prefix = e.data
            e.data = ""
        f = File(t[0][1])
        f.printme = None
        for name, value in attrs:
            setattr(f, name, value)
        e.files.append(f)
        self.afunclist.append(f.afunc)

    def end_file(self):
        self.e.data = ""
        self.afunclist = self.afunclist[:-1]

    def start_directory(self, attrs):
        try:
            e = self.e
        except AttributeError:
            self.error("<directory> tag outside of <scons_example>")
        t = filter(lambda t: t[0] == 'name', attrs)
        if not t:
            self.error("no <directory> name attribute found")
        try:
            e.prefix
        except AttributeError:
            e.prefix = e.data
            e.data = ""
        d = Directory(t[0][1])
        for name, value in attrs:
            setattr(d, name, value)
        e.dirs.append(d)
        self.afunclist.append(d.afunc)

    def end_directory(self):
        self.e.data = ""
        self.afunclist = self.afunclist[:-1]

    def start_scons_example_file(self, attrs):
        t = filter(lambda t: t[0] == 'example', attrs)
        if not t:
            self.error("no <scons_example_file> example attribute found")
        exname = t[0][1]
        try:
            e = self.examples[exname]
        except KeyError:
            self.error("unknown example name '%s'" % exname)
        fattrs = filter(lambda t: t[0] == 'name', attrs)
        if not fattrs:
            self.error("no <scons_example_file> name attribute found")
        fname = fattrs[0][1]
        f = filter(lambda f, fname=fname: f.name == fname, e.files)
        if not f:
            self.error("example '%s' does not have a file named '%s'" % (exname, fname))
        self.f = f[0]

    def end_scons_example_file(self):
        f = self.f
        sys.stdout.write('<programlisting>')
        i = len(f.data) - 1
        while f.data[i] == ' ':
            i = i - 1
        sys.stdout.write(f.data[:i+1] + '</programlisting>')
        delattr(self, 'f')

    def start_scons_output(self, attrs):
        t = filter(lambda t: t[0] == 'example', attrs)
        if not t:
            self.error("no <scons_output> example attribute found")
        exname = t[0][1]
        try:
            e = self.examples[exname]
        except KeyError:
            self.error("unknown example name '%s'" % exname)
        # Default values for an example.
        o = Output()
        o.os = 'posix'
        o.e = e
        # Locally-set.
        for name, value in attrs:
            setattr(o, name, value)
        self.o = o
        self.afunclist.append(o.afunc)

    def end_scons_output(self):
        o = self.o
        e = o.e
        t = TestCmd.TestCmd(workdir='', combine=1)
        t.subdir('ROOT', 'WORK')
        for d in e.dirs:
            dir = t.workpath('WORK', d.name)
            if not os.path.exists(dir):
                os.makedirs(dir)
        for f in e.files:
            i = 0
            while f.data[i] == '\n':
                i = i + 1
            lines = string.split(f.data[i:], '\n')
            i = 0
            while lines[0][i] == ' ':
                i = i + 1
            lines = map(lambda l, i=i: l[i:], lines)
            path = string.replace(f.name, '__ROOT__', t.workpath('ROOT'))
            dir, name = os.path.split(f.name)
            if dir:
                dir = t.workpath('WORK', dir)
                if not os.path.exists(dir):
                    os.makedirs(dir)
            content = string.join(lines, '\n')
            content = string.replace(content,
                                     '__ROOT__',
                                     t.workpath('ROOT'))
            t.write(t.workpath('WORK', f.name), content)
        i = len(o.prefix)
        while o.prefix[i-1] != '\n':
            i = i - 1
        sys.stdout.write('<literallayout>' + o.prefix[:i])
        p = o.prefix[i:]
        for c in o.commandlist:
            sys.stdout.write(p + Prompt[o.os])
            d = string.replace(c.data, '__ROOT__', '')
            sys.stdout.write('<userinput>' + d + '</userinput>\n')
            e = string.replace(c.data, '__ROOT__', t.workpath('ROOT'))
            args = string.split(e)[1:]
            os.environ['SCONS_LIB_DIR'] = scons_lib_dir
            t.run(interpreter = sys.executable,
                  program = scons_py,
                  arguments = '-f - ' + string.join(args),
                  chdir = t.workpath('WORK'),
                  stdin = Stdin % o.os)
            out = string.replace(t.stdout(), t.workpath('ROOT'), '')
            if out:
                lines = string.split(out, '\n')
                if lines:
                    while lines[-1] == '':
                        lines = lines[:-1]
                    for l in lines:
                        sys.stdout.write(p + l + '\n')
            #err = t.stderr()
            #if err:
            #    sys.stderr.write(err)
        if o.data[0] == '\n':
            o.data = o.data[1:]
        sys.stdout.write(o.data + '</literallayout>')
        delattr(self, 'o')
        self.afunclist = self.afunclist[:-1]

    def start_command(self, attrs):
        try:
            o = self.o
        except AttributeError:
            self.error("<command> tag outside of <scons_output>")
        try:
            o.prefix
        except AttributeError:
            o.prefix = o.data
            o.data = ""
        c = Command()
        o.commandlist.append(c)
        self.afunclist.append(c.afunc)

    def end_command(self):
        self.o.data = ""
        self.afunclist = self.afunclist[:-1]

    def start_sconstruct(self, attrs):
        sys.stdout.write('<programlisting>')

    def end_sconstruct(self):
        sys.stdout.write('</programlisting>')

try:
    file = sys.argv[1]
except IndexError:
    file = '-'

if file == '-':
    f = sys.stdin
else:
    try:
        f = open(file, 'r')
    except IOError, msg:
        print file, ":", msg
        sys.exit(1)

data = f.read()
if f is not sys.stdin:
    f.close()

x = MySGML()
for c in data:
    x.feed(c)
x.close()