[ci skip] Add note that this change may break SetOption() + ninja usage with fix
[scons.git] / bin / SConsExamples.py
blobe7a20ce2ea2507b9f6ee38d86df1c5ae12921004
1 # !/usr/bin/env python
3 # Copyright (c) 2010 The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 # This script looks for some XML tags that describe SCons example
27 # configurations and commands to execute in those configurations, and
28 # uses TestCmd.py to execute the commands and insert the output from
29 # those commands into the XML that we output. This way, we can run a
30 # script and update all of our example documentation output without
31 # a lot of laborious by-hand checking.
33 # An "SCons example" looks like this, and essentially describes a set of
34 # input files (program source files as well as SConscript files):
36 # <scons_example name="ex1">
37 # <file name="SConstruct" printme="1">
38 # env = Environment()
39 # env.Program('foo')
40 # </file>
41 # <file name="foo.c">
42 # int main(void) { printf("foo.c\n"); }
43 # </file>
44 # </scons_example>
46 # The <file> contents within the <scons_example> tag will get written
47 # into a temporary directory whenever example output needs to be
48 # generated. By default, the <file> contents are not inserted into text
49 # directly, unless you set the "printme" attribute on one or more files,
50 # in which case they will get inserted within a <programlisting> tag.
51 # This makes it easy to define the example at the appropriate
52 # point in the text where you intend to show the SConstruct file.
54 # Note that you should usually give the <scons_example> a "name"
55 # attribute so that you can refer to the example configuration later to
56 # run SCons and generate output.
58 # If you just want to show a file's contents without worry about running
59 # SCons, there's a shorter <sconstruct> tag:
61 # <sconstruct>
62 # env = Environment()
63 # env.Program('foo')
64 # </sconstruct>
66 # This is essentially equivalent to <scons_example><file printme="1">,
67 # but it's more straightforward.
69 # SCons output is generated from the following sort of tag:
71 # <scons_output example="ex1" os="posix">
72 # <scons_output_command suffix="1">scons -Q foo</scons_output_command>
73 # <scons_output_command suffix="2">scons -Q foo</scons_output_command>
74 # </scons_output>
76 # You tell it which example to use with the "example" attribute, and then
77 # give it a list of <scons_output_command> tags to execute. You can also
78 # supply an "os" tag, which specifies the type of operating system this
79 # example is intended to show; if you omit this, default value is "posix".
81 # The generated XML will show the command line (with the appropriate
82 # command-line prompt for the operating system), execute the command in
83 # a temporary directory with the example files, capture the standard
84 # output from SCons, and insert it into the text as appropriate.
85 # Error output gets passed through to your error output so you
86 # can see if there are any problems executing the command.
89 import os
90 import re
91 import sys
92 import time
94 import SConsDoc
95 from SConsDoc import tf as stf
98 # The available types for ExampleFile entries
100 FT_FILE = 0 # a physical file (=<file>)
101 FT_FILEREF = 1 # a reference (=<scons_example_file>)
103 class ExampleFile:
104 def __init__(self, type_=FT_FILE):
105 self.type = type_
106 self.name = ''
107 self.content = ''
108 self.chmod = ''
110 def isFileRef(self):
111 return self.type == FT_FILEREF
113 class ExampleFolder:
114 def __init__(self):
115 self.name = ''
116 self.chmod = ''
118 class ExampleCommand:
119 def __init__(self):
120 self.edit = None
121 self.environment = ''
122 self.output = ''
123 self.cmd = ''
125 class ExampleOutput:
126 def __init__(self):
127 self.name = ''
128 self.tools = ''
129 self.os = 'posix'
130 self.preserve = None
131 self.suffix = ''
132 self.commands = []
134 class ExampleInfo:
135 def __init__(self):
136 self.name = ''
137 self.files = []
138 self.folders = []
139 self.outputs = []
141 def getFileContents(self, fname):
142 for f in self.files:
143 if fname == f.name and not f.isFileRef():
144 return f.content
146 return ''
148 def readExampleInfos(fpath, examples):
149 """ Add the example infos for the file fpath to the
150 global dictionary examples.
153 # Create doctree
154 t = SConsDoc.SConsDocTree()
155 t.parseXmlFile(fpath)
157 # Parse scons_examples
158 for e in stf.findAll(t.root, "scons_example", SConsDoc.dbxid,
159 t.xpath_context, t.nsmap):
160 n = ''
161 if stf.hasAttribute(e, 'name'):
162 n = stf.getAttribute(e, 'name')
163 if n and n not in examples:
164 i = ExampleInfo()
165 i.name = n
166 examples[n] = i
168 # Parse file and directory entries
169 for f in stf.findAll(e, "file", SConsDoc.dbxid,
170 t.xpath_context, t.nsmap):
171 fi = ExampleFile()
172 if stf.hasAttribute(f, 'name'):
173 fi.name = stf.getAttribute(f, 'name')
174 if stf.hasAttribute(f, 'chmod'):
175 fi.chmod = stf.getAttribute(f, 'chmod')
176 fi.content = stf.getText(f)
177 examples[n].files.append(fi)
178 for d in stf.findAll(e, "directory", SConsDoc.dbxid,
179 t.xpath_context, t.nsmap):
180 di = ExampleFolder()
181 if stf.hasAttribute(d, 'name'):
182 di.name = stf.getAttribute(d, 'name')
183 if stf.hasAttribute(d, 'chmod'):
184 di.chmod = stf.getAttribute(d, 'chmod')
185 examples[n].folders.append(di)
188 # Parse scons_example_files
189 for f in stf.findAll(t.root, "scons_example_file", SConsDoc.dbxid,
190 t.xpath_context, t.nsmap):
191 if stf.hasAttribute(f, 'example'):
192 e = stf.getAttribute(f, 'example')
193 else:
194 continue
195 fi = ExampleFile(FT_FILEREF)
196 if stf.hasAttribute(f, 'name'):
197 fi.name = stf.getAttribute(f, 'name')
198 if stf.hasAttribute(f, 'chmod'):
199 fi.chmod = stf.getAttribute(f, 'chmod')
200 fi.content = stf.getText(f)
201 examples[e].files.append(fi)
204 # Parse scons_output
205 for o in stf.findAll(t.root, "scons_output", SConsDoc.dbxid,
206 t.xpath_context, t.nsmap):
207 if stf.hasAttribute(o, 'example'):
208 n = stf.getAttribute(o, 'example')
209 else:
210 continue
212 eout = ExampleOutput()
213 if stf.hasAttribute(o, 'name'):
214 eout.name = stf.getAttribute(o, 'name')
215 if stf.hasAttribute(o, 'tools'):
216 eout.tools = stf.getAttribute(o, 'tools')
217 if stf.hasAttribute(o, 'os'):
218 eout.os = stf.getAttribute(o, 'os')
219 if stf.hasAttribute(o, 'suffix'):
220 eout.suffix = stf.getAttribute(o, 'suffix')
222 for c in stf.findAll(o, "scons_output_command", SConsDoc.dbxid,
223 t.xpath_context, t.nsmap):
224 oc = ExampleCommand()
225 if stf.hasAttribute(c, 'edit'):
226 oc.edit = stf.getAttribute(c, 'edit')
227 if stf.hasAttribute(c, 'environment'):
228 oc.environment = stf.getAttribute(c, 'environment')
229 if stf.hasAttribute(c, 'output'):
230 oc.output = stf.getAttribute(c, 'output')
231 if stf.hasAttribute(c, 'cmd'):
232 oc.cmd = stf.getAttribute(c, 'cmd')
233 else:
234 oc.cmd = stf.getText(c)
236 eout.commands.append(oc)
238 examples[n].outputs.append(eout)
240 def readAllExampleInfos(dpath):
241 """ Scan for XML files in the given directory and
242 collect together all relevant infos (files/folders,
243 output commands) in a map, which gets returned.
245 examples = {}
246 for path, dirs, files in os.walk(dpath):
247 for f in files:
248 if f.endswith('.xml'):
249 fpath = os.path.join(path, f)
250 if SConsDoc.isSConsXml(fpath):
251 readExampleInfos(fpath, examples)
253 return examples
255 generated_examples = os.path.join('doc', 'generated', 'examples')
257 def ensureExampleOutputsExist(dpath):
258 """ Scan for XML files in the given directory and
259 ensure that for every example output we have a
260 corresponding output file in the 'generated/examples'
261 folder.
263 # Ensure that the output folder exists
264 if not os.path.isdir(generated_examples):
265 os.mkdir(generated_examples)
267 examples = readAllExampleInfos(dpath)
268 for key, value in examples.items():
269 # Process all scons_output tags
270 for o in value.outputs:
271 cpath = os.path.join(generated_examples,
272 key + '_' + o.suffix + '.xml')
273 if not os.path.isfile(cpath):
274 # Start new XML file
275 s = stf.newXmlTree("screen")
276 stf.setText(s, "NO OUTPUT YET! Run the script to generate/update all examples.")
277 # Write file
278 stf.writeTree(s, cpath)
280 # Process all scons_example_file tags
281 for r in value.files:
282 if r.isFileRef():
283 # Get file's content
284 content = value.getFileContents(r.name)
285 fpath = os.path.join(generated_examples,
286 key + '_' + r.name.replace("/", "_"))
287 # Write file
288 with open(fpath, 'w') as f:
289 f.write("%s\n" % content)
291 perc = "%"
293 def createAllExampleOutputs(dpath):
294 """ Scan for XML files in the given directory and
295 creates all output files for every example in
296 the 'generated/examples' folder.
298 # Ensure that the output folder exists
299 if not os.path.isdir(generated_examples):
300 os.mkdir(generated_examples)
302 examples = readAllExampleInfos(dpath)
303 total = len(examples)
304 idx = 0
306 if len(sys.argv) > 1:
307 examples_to_run = sys.argv[1:]
308 examples = { k:v for k,v in examples.items() if k in examples_to_run }
310 for key, value in examples.items():
311 # Process all scons_output tags
312 print("%.2f%s (%d/%d) %s" % (float(idx + 1) * 100.0 / float(total),
313 perc, idx + 1, total, key))
315 create_scons_output(value)
316 # Process all scons_example_file tags
317 for r in value.files:
318 if r.isFileRef():
319 # Get file's content
320 content = value.getFileContents(r.name)
321 fpath = os.path.join(generated_examples,
322 key + '_' + r.name.replace("/", "_"))
323 # Write file
324 with open(fpath, 'w') as f:
325 f.write("%s\n" % content)
326 idx += 1
328 def collectSConsExampleNames(fpath):
329 """ Return a set() of example names, used in the given file fpath.
331 names = set()
332 suffixes = {}
333 failed_suffixes = False
335 # Create doctree
336 t = SConsDoc.SConsDocTree()
337 t.parseXmlFile(fpath)
339 # Parse it
340 for e in stf.findAll(t.root, "scons_example", SConsDoc.dbxid,
341 t.xpath_context, t.nsmap):
342 n = ''
343 if stf.hasAttribute(e, 'name'):
344 n = stf.getAttribute(e, 'name')
345 if n:
346 names.add(n)
347 if n not in suffixes:
348 suffixes[n] = []
349 else:
350 print("Error: Example in file '%s' is missing a name!" % fpath)
351 failed_suffixes = True
353 for o in stf.findAll(t.root, "scons_output", SConsDoc.dbxid,
354 t.xpath_context, t.nsmap):
355 n = ''
356 if stf.hasAttribute(o, 'example'):
357 n = stf.getAttribute(o, 'example')
358 else:
359 print("Error: scons_output in file '%s' is missing an example name!" % fpath)
360 failed_suffixes = True
362 if n not in suffixes:
363 print("Error: scons_output in file '%s' is referencing non-existent example '%s'!" % (fpath, n))
364 failed_suffixes = True
365 continue
367 s = ''
368 if stf.hasAttribute(o, 'suffix'):
369 s = stf.getAttribute(o, 'suffix')
370 else:
371 print("Error: scons_output in file '%s' (example '%s') is missing a suffix!" % (fpath, n))
372 failed_suffixes = True
374 if s not in suffixes[n]:
375 suffixes[n].append(s)
376 else:
377 print("Error: scons_output in file '%s' (example '%s') is using a duplicate suffix '%s'!" % (fpath, n, s))
378 failed_suffixes = True
380 return names, failed_suffixes
382 def exampleNamesAreUnique(dpath):
383 """ Scan for XML files in the given directory and
384 check whether the scons_example names are unique.
386 unique = True
387 allnames = set()
388 for path, dirs, files in os.walk(dpath):
389 for f in files:
390 if f.endswith('.xml'):
391 fpath = os.path.join(path, f)
392 if SConsDoc.isSConsXml(fpath):
393 names, failed_suffixes = collectSConsExampleNames(fpath)
394 if failed_suffixes:
395 unique = False
396 i = allnames.intersection(names)
397 if i:
398 print("Not unique in %s are: %s" % (fpath, ', '.join(i)))
399 unique = False
401 allnames |= names
403 return unique
405 # ###############################################################
407 # In the second half of this module (starting here)
408 # we define the variables and functions that are required
409 # to actually run the examples, collect their output and
410 # write it into the files in doc/generated/examples...
411 # which then get included by our UserGuide.
413 # ###############################################################
415 sys.path.append(os.path.join(os.getcwd(), 'testing/framework'))
416 sys.path.append(os.path.join(os.getcwd(), 'build', 'testing/framework'))
418 scons_py = os.path.join('scripts', 'scons.py')
419 scons_py = os.path.join(os.getcwd(), scons_py)
420 scons_lib_dir = os.path.join(os.getcwd(), 'SCons')
422 os.environ['SCONS_LIB_DIR'] = scons_lib_dir
424 import TestCmd
426 Prompt = {
427 'posix' : '% ',
428 'win32' : 'C:\\>'
431 # The magick SCons hackery that makes this work.
433 # So that our examples can still use the default SConstruct file, we
434 # actually feed the following into SCons via stdin and then have it
435 # SConscript() the SConstruct file. This stdin wrapper creates a set
436 # of ToolSurrogates for the tools for the appropriate platform. These
437 # Surrogates print output like the real tools and behave like them
438 # without actually having to be on the right platform or have the right
439 # tool installed.
441 # The upshot: The wrapper transparently changes the world out from
442 # under the top-level SConstruct file in an example just so we can get
443 # the command output.
445 Stdin = """\
446 import os
447 import re
448 import SCons.Action
449 import SCons.Defaults
450 import SCons.Node.FS
451 import shutil
453 platform = '%(osname)s'
455 Sep = {
456 'posix' : '/',
457 'win32' : '\\\\',
458 }[platform]
461 # Slip our own __str__() method into the EntryProxy class used to expand
462 # $TARGET{S} and $SOURCE{S} to translate the path-name separators from
463 # what's appropriate for the system we're running on to what's appropriate
464 # for the example system.
465 orig = SCons.Node.FS.EntryProxy
466 class MyEntryProxy(orig):
467 def __str__(self):
468 return str(self._subject).replace(os.sep, Sep)
469 SCons.Node.FS.EntryProxy = MyEntryProxy
471 # Slip our own RDirs() method into the Node.FS.File class so that the
472 # expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name
473 # separators translated from what's appropriate for the system we're
474 # running on to what's appropriate for the example system.
475 orig_RDirs = SCons.Node.FS.File.RDirs
476 def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs):
477 return [str(x).replace(os.sep, Sep) for x in orig_RDirs(self, pathlist)]
478 SCons.Node.FS.File.RDirs = my_RDirs
480 class Curry:
481 def __init__(self, fun, *args, **kwargs):
482 self.fun = fun
483 self.pending = args[:]
484 self.kwargs = kwargs.copy()
486 def __call__(self, *args, **kwargs):
487 if kwargs and self.kwargs:
488 kw = self.kwargs.copy()
489 kw.update(kwargs)
490 else:
491 kw = kwargs or self.kwargs
493 return self.fun(*self.pending + args, **kw)
495 def Str(target, source, env, cmd=""):
496 result = []
497 for cmd in env.subst_list(cmd, target=target, source=source):
498 result.append(' '.join(map(str, cmd)))
499 return '\\n'.join(result)
501 class ToolSurrogate:
502 def __init__(self, tool, variable, func, varlist):
503 self.tool = tool
504 if not isinstance(variable, list):
505 variable = [variable]
506 self.variable = variable
507 self.func = func
508 self.varlist = varlist
509 def __call__(self, env):
510 t = Tool(self.tool)
511 t.generate(env)
512 for v in self.variable:
513 orig = env[v]
514 try:
515 strfunction = orig.strfunction
516 except AttributeError:
517 strfunction = Curry(Str, cmd=orig)
518 # Don't call Action() through its global function name, because
519 # that leads to infinite recursion in trying to initialize the
520 # Default Environment.
521 env[v] = SCons.Action.Action(self.func,
522 strfunction=strfunction,
523 varlist=self.varlist)
524 def __repr__(self):
525 # This is for the benefit of printing the 'TOOLS'
526 # variable through env.Dump().
527 return repr(self.tool)
529 def Null(target, source, env):
530 pass
532 def Cat(target, source, env):
533 target = str(target[0])
534 for src in map(str, source):
535 shutil.copy(src, target)
537 def CCCom(target, source, env):
538 def process(source_file, ofp):
539 with open(source_file, "r") as ifp:
540 for line in ifp.readlines():
541 m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
542 if m:
543 include = m.group(1)
544 for d in [str(env.Dir('$CPPPATH')), '.']:
545 f = os.path.join(d, include)
546 if os.path.exists(f):
547 process(f, ofp)
548 break
549 elif line[:11] != "STRIP CCCOM":
550 ofp.write(line)
552 with open(str(target[0]), "w") as fp:
553 for src in map(str, source):
554 process(src, fp)
555 fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
557 public_class_re = re.compile(r'^public class (\S+)', re.MULTILINE)
559 def JavaCCom(target, source, env):
560 # This is a fake Java compiler that just looks for
561 # public class FooBar
562 # lines in the source file(s) and spits those out
563 # to .class files named after the class.
564 tlist = list(map(str, target))
565 not_copied = {}
566 for t in tlist:
567 not_copied[t] = 1
568 for src in map(str, source):
569 with open(src, "r") as f:
570 contents = f.read()
571 classes = public_class_re.findall(contents)
572 for c in classes:
573 for t in [x for x in tlist if x.find(c) != -1]:
574 with open(t, "w") as f:
575 f.write(contents)
576 del not_copied[t]
577 for t in not_copied.keys():
578 with open(t, "w") as f:
579 f.write("\\n")
581 def JavaHCom(target, source, env):
582 tlist = map(str, target)
583 slist = map(str, source)
584 for t, s in zip(tlist, slist):
585 shutil.copy(s, t)
587 def JarCom(target, source, env):
588 target = str(target[0])
589 class_files = []
590 for src in map(str, source):
591 for dirpath, dirnames, filenames in os.walk(src):
592 class_files.extend([ os.path.join(dirpath, f)
593 for f in filenames if f.endswith('.class') ])
594 for cf in class_files:
595 shutil.copy(cf, target)
597 # XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand
598 # here is bogus. It's for the benefit of doc/user/command-line.in, which
599 # uses examples that want to rebuild based on changes to these variables.
600 # It would be better to figure out a way to do it based on the content of
601 # the generated command-line, or else find a way to let the example markup
602 # language in doc/user/command-line.in tell this script what variables to
603 # add, but that's more difficult than I want to figure out how to do right
604 # now, so let's just use the simple brute force approach for the moment.
606 ToolList = {
607 'posix' : [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
608 ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []),
609 ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []),
610 ('tar', 'TARCOM', Null, []),
611 ('zip', 'ZIPCOM', Null, []),
612 ('javac', 'JAVACCOM', JavaCCom, []),
613 ('javah', 'JAVAHCOM', JavaHCom, []),
614 ('jar', 'JARCOM', JarCom, []),
615 ('rmic', 'RMICCOM', Cat, []),
617 'win32' : [('msvc', ['CCCOM', 'SHCCCOM', 'RCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
618 ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []),
619 ('mslib', 'ARCOM', Cat, []),
620 ('tar', 'TARCOM', Null, []),
621 ('zip', 'ZIPCOM', Null, []),
622 ('javac', 'JAVACCOM', JavaCCom, []),
623 ('javah', 'JAVAHCOM', JavaHCom, []),
624 ('jar', 'JARCOM', JarCom, []),
625 ('rmic', 'RMICCOM', Cat, []),
629 toollist = ToolList[platform]
630 filter_tools = '%(tools)s'.split()
631 if filter_tools:
632 toollist = [x for x in toollist if x[0] in filter_tools]
634 toollist = [ToolSurrogate(*t) for t in toollist]
636 toollist.append('install')
638 def surrogate_spawn(sh, escape, cmd, args, env):
639 pass
641 def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr):
642 pass
644 SCons.Defaults.ConstructionEnvironment.update({
645 'PLATFORM' : platform,
646 'TOOLS' : toollist,
647 'SPAWN' : surrogate_spawn,
648 'PSPAWN' : surrogate_pspawn,
651 SConscript('SConstruct')
654 # "Commands" that we will execute in our examples.
655 def command_scons(args, command, test, values):
657 Fake scons command
659 save_vals = {}
660 delete_keys = []
661 try:
662 ce = command.environment
663 except AttributeError:
664 pass
665 else:
666 for arg in command.environment.split():
667 key, val = arg.split('=')
668 try:
669 save_vals[key] = os.environ[key]
670 except KeyError:
671 delete_keys.append(key)
672 os.environ[key] = val
674 test.write(test.workpath('WORK/SConstruct_created'), Stdin % values)
676 test.run(interpreter=sys.executable,
677 program=scons_py,
678 # We use ToolSurrogates to capture win32 output by "building"
679 # examples using a fake win32 tool chain. Suppress the
680 # warnings that come from the new revamped VS support so
681 # we can build doc on (Linux) systems that don't have
682 # Visual C installed.
683 arguments='--warn=no-visual-c-missing -f - ' + ' '.join(args),
684 chdir=test.workpath('WORK'),
685 stdin=Stdin % values)
686 os.environ.update(save_vals)
687 for key in delete_keys:
688 del(os.environ[key])
689 out = test.stdout()
690 out = out.replace(test.workpath('ROOT'), '')
691 out = out.replace(test.workpath('WORK/SConstruct'),
692 '/home/my/project/SConstruct')
693 lines = out.split('\n')
694 if lines:
695 while lines[-1] == '':
696 lines = lines[:-1]
697 # err = test.stderr()
698 # if err:
699 # sys.stderr.write(err)
700 return lines
702 def command_touch(args, command, test, values):
703 if args[0] == '-t':
704 t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
705 times = (t, t)
706 args = args[2:]
707 else:
708 time.sleep(1)
709 times = None
710 for file in args:
711 if not os.path.isabs(file):
712 file = os.path.join(test.workpath('WORK'), file)
713 if not os.path.exists(file):
714 with open(file, 'w'):
715 pass
716 os.utime(file, times)
717 return []
719 def command_edit(args, c, test, values):
720 if c.edit is None:
721 add_string = 'void edit(void) { ; }\n'
722 else:
723 add_string = c.edit[:]
724 if add_string[-1] != '\n':
725 add_string = add_string + '\n'
726 for file in args:
727 if not os.path.isabs(file):
728 file = os.path.join(test.workpath('WORK'), file)
729 with open(file, 'a') as f:
730 f.write(add_string)
731 return []
733 def command_ls(args, c, test, values):
734 def ls(a):
735 try:
736 return [' '.join(sorted([x for x in os.listdir(a) if x[0] != '.']))]
737 except OSError as e:
738 # This should never happen. Pop into debugger
739 import pdb; pdb.set_trace()
740 if args:
741 l = []
742 for a in args:
743 l.extend(ls(test.workpath('WORK', a)))
744 return l
745 return ls(test.workpath('WORK'))
747 def command_sleep(args, c, test, values):
748 time.sleep(int(args[0]))
750 CommandDict = {
751 'scons' : command_scons,
752 'touch' : command_touch,
753 'edit' : command_edit,
754 'ls' : command_ls,
755 'sleep' : command_sleep,
758 def ExecuteCommand(args, c, t, values):
759 try:
760 func = CommandDict[args[0]]
761 except KeyError:
762 func = lambda args, c, t, values: []
763 return func(args[1:], c, t, values)
766 def create_scons_output(e):
768 The real raison d'etre for this script, this is where we
769 actually execute SCons to fetch the output.
772 # Loop over all outputs for the example
773 for o in e.outputs:
774 # Create new test directory
775 t = TestCmd.TestCmd(workdir='', combine=1)
776 if o.preserve:
777 t.preserve()
778 t.subdir('ROOT', 'WORK')
779 t.rootpath = t.workpath('ROOT').replace('\\', '\\\\')
781 for d in e.folders:
782 dir = t.workpath('WORK', d.name)
783 if not os.path.exists(dir):
784 os.makedirs(dir)
786 for f in e.files:
787 if f.isFileRef():
788 continue
790 # Left-align file's contents, starting on the first
791 # non-empty line
793 data = f.content.split('\n')
794 i = 0
795 # Skip empty lines
796 while data[i] == '':
797 i = i + 1
798 lines = data[i:]
799 i = 0
800 # Scan first line for the number of spaces
801 # that this block is indented
802 while lines[0][i] == ' ':
803 i = i + 1
804 # Left-align block
805 lines = [l[i:] for l in lines]
806 path = f.name.replace('__ROOT__', t.rootpath)
807 if not os.path.isabs(path):
808 path = t.workpath('WORK', path)
809 dir, name = os.path.split(path)
810 if dir and not os.path.exists(dir):
811 os.makedirs(dir)
812 content = '\n'.join(lines)
813 content = content.replace('__ROOT__', t.rootpath)
814 path = t.workpath('WORK', path)
815 t.write(path, content)
816 if hasattr(f, 'chmod'):
817 if len(f.chmod):
818 os.chmod(path, int(f.chmod, base=8))
820 # Regular expressions for making the doc output consistent,
821 # regardless of reported addresses or Python version.
823 # Massage addresses in object repr strings to a constant.
824 address_re = re.compile(r' at 0x[0-9a-fA-F]*>')
826 # Massage file names in stack traces (sometimes reported as absolute
827 # paths) to a consistent relative path.
828 engine_re = re.compile(r' File ".*/SCons/')
830 # Python 2.5 changed the stack trace when the module is read
831 # from standard input from read "... line 7, in ?" to
832 # "... line 7, in <module>".
833 file_re = re.compile(r'^( *File ".*", line \d+, in) \?$', re.M)
835 # Python 2.6 made UserList a new-style class, which changes the
836 # AttributeError message generated by our NodeList subclass.
837 nodelist_re = re.compile(r'(AttributeError:) NodeList instance (has no attribute \S+)')
839 # Root element for our subtree
840 sroot = stf.newEtreeNode("screen", True)
841 curchild = None
842 content = ""
843 for command in o.commands:
844 content += Prompt[o.os]
845 if curchild is not None:
846 if not command.output:
847 # Append content as tail
848 curchild.tail = content
849 content = "\n"
850 # Add new child for userinput tag
851 curchild = stf.newEtreeNode("userinput")
852 d = command.cmd.replace('__ROOT__', '')
853 curchild.text = d
854 sroot.append(curchild)
855 else:
856 content += command.output + '\n'
857 else:
858 if not command.output:
859 # Add first text to root
860 sroot.text = content
861 content = "\n"
862 # Add new child for userinput tag
863 curchild = stf.newEtreeNode("userinput")
864 d = command.cmd.replace('__ROOT__', '')
865 curchild.text = d
866 sroot.append(curchild)
867 else:
868 content += command.output + '\n'
869 # Execute command and capture its output
870 cmd_work = command.cmd.replace('__ROOT__', t.workpath('ROOT'))
871 args = cmd_work.split()
872 lines = ExecuteCommand(args, command, t, {'osname':o.os, 'tools':o.tools})
873 if not command.output and lines:
874 ncontent = '\n'.join(lines)
875 ncontent = address_re.sub(r' at 0x700000>', ncontent)
876 ncontent = engine_re.sub(r' File "SCons/', ncontent)
877 ncontent = file_re.sub(r'\1 <module>', ncontent)
878 ncontent = nodelist_re.sub(r"\1 'NodeList' object \2", ncontent)
879 ncontent = ncontent.replace('__ROOT__', '')
880 content += ncontent + '\n'
881 # Add last piece of content
882 if len(content):
883 if curchild is not None:
884 curchild.tail = content
885 else:
886 sroot.text = content
888 # Construct filename
889 fpath = os.path.join(generated_examples,
890 e.name + '_' + o.suffix + '.xml')
891 # Expand Element tree
892 s = stf.decorateWithHeader(stf.convertElementTree(sroot)[0])
893 # Write it to file
894 stf.writeTree(s, fpath)
897 # Local Variables:
898 # tab-width:4
899 # indent-tabs-mode:nil
900 # End:
901 # vim: set expandtab tabstop=4 shiftwidth=4: