7fe68be3a9988cadc9494358f3ff34ca5bedfbfa
[scons.git] / QMTest / TestSCons.py
1 """
2 TestSCons.py:  a testing framework for the SCons software construction
3 tool.
4
5 A TestSCons environment object is created via the usual invocation:
6
7     test = TestSCons()
8
9 TestScons is a subclass of TestCommon, which is in turn is a subclass
10 of TestCmd), and hence has available all of the methods and attributes
11 from those classes, as well as any overridden or additional methods or
12 attributes defined in this subclass.
13 """
14
15 # __COPYRIGHT__
16
17 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
18
19 import os
20 import os.path
21 import re
22 import string
23 import sys
24 import time
25
26 import __builtin__
27 try:
28     __builtin__.zip
29 except AttributeError:
30     def zip(*lists):
31         result = []
32         for i in xrange(len(lists[0])):
33             result.append(tuple(map(lambda l, i=i: l[i], lists)))
34         return result
35     __builtin__.zip = zip
36
37 from TestCommon import *
38 from TestCommon import __all__
39
40 # Some tests which verify that SCons has been packaged properly need to
41 # look for specific version file names.  Replicating the version number
42 # here provides some independent verification that what we packaged
43 # conforms to what we expect.
44
45 default_version = '0.98.1'
46
47 copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008'
48
49 # In the checked-in source, the value of SConsVersion in the following
50 # line must remain "__ VERSION __" (without the spaces) so the built
51 # version in build/QMTest/TestSCons.py contains the actual version
52 # string of the packages that have been built.
53 SConsVersion = '__VERSION__'
54 if SConsVersion == '__' + 'VERSION' + '__':
55     SConsVersion = default_version
56
57 __all__.extend([ 'TestSCons',
58                  'machine',
59                  'python',
60                  '_exe',
61                  '_obj',
62                  '_shobj',
63                  'shobj_',
64                  'lib_',
65                  '_lib',
66                  'dll_',
67                  '_dll'
68                ])
69
70 machine_map = {
71     'i686'  : 'i386',
72     'i586'  : 'i386',
73     'i486'  : 'i386',
74 }
75
76 try:
77     uname = os.uname
78 except AttributeError:
79     # Windows doesn't have a uname() function.  We could use something like
80     # sys.platform as a fallback, but that's not really a "machine," so
81     # just leave it as None.
82     machine = None
83 else:
84     machine = uname()[4]
85     machine = machine_map.get(machine, machine)
86
87 python = python_executable
88 _python_ = '"' + python_executable + '"'
89 _exe = exe_suffix
90 _obj = obj_suffix
91 _shobj = shobj_suffix
92 shobj_ = shobj_prefix
93 _lib = lib_suffix
94 lib_ = lib_prefix
95 _dll = dll_suffix
96 dll_ = dll_prefix
97
98 def gccFortranLibs():
99     """Test whether -lfrtbegin is required.  This can probably be done in
100     a more reliable way, but using popen3 is relatively efficient."""
101
102     libs = ['g2c']
103
104     try:
105         import popen2
106         stderr = popen2.popen3('gcc -v')[2]
107     except OSError:
108         return libs
109
110     for l in stderr.readlines():
111         list = string.split(l)
112         if len(list) > 3 and list[:2] == ['gcc', 'version']:
113             if list[2][:2] in ('3.', '4.'):
114                 libs = ['frtbegin'] + libs
115                 break
116     return libs
117
118
119 if sys.platform == 'cygwin':
120     # On Cygwin, os.path.normcase() lies, so just report back the
121     # fact that the underlying Win32 OS is case-insensitive.
122     def case_sensitive_suffixes(s1, s2):
123         return 0
124 else:
125     def case_sensitive_suffixes(s1, s2):
126         return (os.path.normcase(s1) != os.path.normcase(s2))
127
128
129 if sys.platform == 'win32':
130     fortran_lib = gccFortranLibs()
131 elif sys.platform == 'cygwin':
132     fortran_lib = gccFortranLibs()
133 elif string.find(sys.platform, 'irix') != -1:
134     fortran_lib = ['ftn']
135 else:
136     fortran_lib = gccFortranLibs()
137
138
139
140 file_expr = r"""File "[^"]*", line \d+, in .+
141 """
142
143 # re.escape escapes too much.
144 def re_escape(str):
145     for c in ['.', '[', ']', '(', ')', '*', '+', '?']:  # Not an exhaustive list.
146         str = string.replace(str, c, '\\' + c)
147     return str
148
149
150
151 try:
152     sys.version_info
153 except AttributeError:
154     # Pre-1.6 Python has no sys.version_info
155     version_string = string.split(sys.version)[0]
156     version_ints = map(int, string.split(version_string, '.'))
157     sys.version_info = tuple(version_ints + ['final', 0])
158
159 def python_version_string():
160     return string.split(sys.version)[0]
161
162 def python_minor_version_string():
163     return sys.version[:3]
164
165 def unsupported_python_version(version=sys.version_info):
166     return version < (1, 5, 2)
167
168 def deprecated_python_version(version=sys.version_info):
169     return version < (2, 2, 0)
170
171 if deprecated_python_version():
172     msg = r"""
173 scons: warning: Support for pre-2.2 Python (%s) is deprecated.
174     If this will cause hardship, contact dev@scons.tigris.org.
175 """
176
177     deprecated_python_expr = re_escape(msg % python_version_string()) + file_expr
178     del msg
179 else:
180     deprecated_python_expr = ""
181
182
183
184 class TestSCons(TestCommon):
185     """Class for testing SCons.
186
187     This provides a common place for initializing SCons tests,
188     eliminating the need to begin every test with the same repeated
189     initializations.
190     """
191
192     scons_version = SConsVersion
193
194     def __init__(self, **kw):
195         """Initialize an SCons testing object.
196
197         If they're not overridden by keyword arguments, this
198         initializes the object with the following default values:
199
200                 program = 'scons' if it exists,
201                           else 'scons.py'
202                 interpreter = 'python'
203                 match = match_exact
204                 workdir = ''
205
206         The workdir value means that, by default, a temporary workspace
207         directory is created for a TestSCons environment.  In addition,
208         this method changes directory (chdir) to the workspace directory,
209         so an explicit "chdir = '.'" on all of the run() method calls
210         is not necessary.
211         """
212         self.orig_cwd = os.getcwd()
213         try:
214             script_dir = os.environ['SCONS_SCRIPT_DIR']
215         except KeyError:
216             pass
217         else:
218             os.chdir(script_dir)
219         if not kw.has_key('program'):
220             kw['program'] = os.environ.get('SCONS')
221             if not kw['program']:
222                 if os.path.exists('scons'):
223                     kw['program'] = 'scons'
224                 else:
225                     kw['program'] = 'scons.py'
226         if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
227             kw['interpreter'] = [python, '-tt']
228         if not kw.has_key('match'):
229             kw['match'] = match_exact
230         if not kw.has_key('workdir'):
231             kw['workdir'] = ''
232
233         # Term causing test failures due to bogus readline init
234         # control character output on FC8
235         # TERM can cause test failures due to control chars in prompts etc.
236         os.environ['TERM'] = 'dumb'
237
238         if deprecated_python_version():
239             sconsflags = os.environ.get('SCONSFLAGS')
240             if sconsflags:
241                 sconsflags = [sconsflags]
242             else:
243                 sconsflags = []
244             sconsflags = sconsflags + ['--warn=no-python-version']
245             os.environ['SCONSFLAGS'] = string.join(sconsflags)
246
247         apply(TestCommon.__init__, [self], kw)
248
249         import SCons.Node.FS
250         if SCons.Node.FS.default_fs is None:
251             SCons.Node.FS.default_fs = SCons.Node.FS.FS()
252
253     def Environment(self, ENV=None, *args, **kw):
254         """
255         Return a construction Environment that optionally overrides
256         the default external environment with the specified ENV.
257         """
258         import SCons.Environment
259         import SCons.Errors
260         if not ENV is None:
261             kw['ENV'] = ENV
262         try:
263             return apply(SCons.Environment.Environment, args, kw)
264         except (SCons.Errors.UserError, SCons.Errors.InternalError):
265             return None
266
267     def detect(self, var, prog=None, ENV=None, norm=None):
268         """
269         Detect a program named 'prog' by first checking the construction
270         variable named 'var' and finally searching the path used by
271         SCons. If either method fails to detect the program, then false
272         is returned, otherwise the full path to prog is returned. If
273         prog is None, then the value of the environment variable will be
274         used as prog.
275         """
276         env = self.Environment(ENV)
277         v = env.subst('$'+var)
278         if not v:
279             return None
280         if prog is None:
281             prog = v
282         if v != prog:
283             return None
284         result = env.WhereIs(prog)
285         if norm and os.sep != '/':
286             result = string.replace(result, os.sep, '/')
287         return result
288
289     def detect_tool(self, tool, prog=None, ENV=None):
290         """
291         Given a tool (i.e., tool specification that would be passed
292         to the "tools=" parameter of Environment()) and a program that
293         corresponds to that tool, return true if and only if we can find
294         that tool using Environment.Detect().
295
296         By default, prog is set to the value passed into the tools parameter.
297         """
298
299         if not prog:
300             prog = tool
301         env = self.Environment(ENV, tools=[tool])
302         if env is None:
303             return None
304         return env.Detect([prog])
305
306     def where_is(self, prog, path=None):
307         """
308         Given a program, search for it in the specified external PATH,
309         or in the actual external PATH is none is specified.
310         """
311         import SCons.Environment
312         env = SCons.Environment.Environment()
313         if path is None:
314             path = os.environ['PATH']
315         return env.WhereIs(prog, path)
316
317     def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0):
318         """Wraps standard output string(s) in the normal
319         "Reading ... done" and "Building ... done" strings
320         """
321         cap,lc = [ ('Build','build'),
322                    ('Clean','clean') ][cleaning]
323         if error:
324             term = "scons: %sing terminated because of errors.\n" % lc
325         else:
326             term = "scons: done %sing targets.\n" % lc
327         return "scons: Reading SConscript files ...\n" + \
328                read_str + \
329                "scons: done reading SConscript files.\n" + \
330                "scons: %sing targets ...\n" % cap + \
331                build_str + \
332                term
333
334     def up_to_date(self, options = None, arguments = None, read_str = "", **kw):
335         s = ""
336         for arg in string.split(arguments):
337             s = s + "scons: `%s' is up to date.\n" % arg
338             if options:
339                 arguments = options + " " + arguments
340         kw['arguments'] = arguments
341         kw['stdout'] = self.wrap_stdout(read_str = read_str, build_str = s)
342         kw['stdout'] = string.replace(kw['stdout'],'\n','\\n')
343         kw['stdout'] = string.replace(kw['stdout'],'.','\\.')
344         kw['match'] = self.match_re_dotall
345         apply(self.run, [], kw)
346
347     def not_up_to_date(self, options = None, arguments = None, **kw):
348         """Asserts that none of the targets listed in arguments is
349         up to date, but does not make any assumptions on other targets.
350         This function is most useful in conjunction with the -n option.
351         """
352         s = ""
353         for  arg in string.split(arguments):
354             s = s + "(?!scons: `%s' is up to date.)" % arg
355             if options:
356                 arguments = options + " " + arguments
357         kw['arguments'] = arguments
358         kw['stdout'] = self.wrap_stdout(build_str="("+s+"[^\n]*\n)*")
359         kw['stdout'] = string.replace(kw['stdout'],'\n','\\n')
360         kw['stdout'] = string.replace(kw['stdout'],'.','\\.')
361         kw['match'] = self.match_re_dotall
362         apply(self.run, [], kw)
363
364     def diff_substr(self, expect, actual, prelen=20, postlen=40):
365         i = 0
366         for x, y in zip(expect, actual):
367             if x != y:
368                 return "Actual did not match expect at char %d:\n" \
369                        "    Expect:  %s\n" \
370                        "    Actual:  %s\n" \
371                        % (i, repr(expect[i-prelen:i+postlen]),
372                              repr(actual[i-prelen:i+postlen]))
373             i = i + 1
374         return "Actual matched the expected output???"
375
376     def python_file_line(self, file, line):
377         """
378         Returns a Python error line for output comparisons.
379
380         The exec of the traceback line gives us the correct format for
381         this version of Python.  Before 2.5, this yielded:
382
383             File "<string>", line 1, ?
384
385         Python 2.5 changed this to:
386
387             File "<string>", line 1, <module>
388
389         We stick the requested file name and line number in the right
390         places, abstracting out the version difference.
391         """
392         exec 'import traceback; x = traceback.format_stack()[-1]'
393         x = string.lstrip(x)
394         x = string.replace(x, '<string>', file)
395         x = string.replace(x, 'line 1,', 'line %s,' % line)
396         return x
397
398     def normalize_pdf(self, s):
399         s = re.sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
400                    r'/\1Date (D:XXXX)', s)
401         s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
402                    r'/ID [<XXXX> <XXXX>]', s)
403         s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
404                    r'/\1 /XXXXXX', s)
405         s = re.sub(r'/Length \d+ *\n/Filter /FlateDecode\n',
406                    r'/Length XXXX\n/Filter /FlateDecode\n', s)
407
408
409         try:
410             import zlib
411         except ImportError:
412             pass
413         else:
414             begin_marker = '/FlateDecode\n>>\nstream\n'
415             end_marker = 'endstream\nendobj'
416
417             encoded = []
418             b = string.find(s, begin_marker, 0)
419             while b != -1:
420                 b = b + len(begin_marker)
421                 e = string.find(s, end_marker, b)
422                 encoded.append((b, e))
423                 b = string.find(s, begin_marker, e + len(end_marker))
424
425             x = 0
426             r = []
427             for b, e in encoded:
428                 r.append(s[x:b])
429                 d = zlib.decompress(s[b:e])
430                 d = re.sub(r'%%CreationDate: [^\n]*\n',
431                            r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
432                 d = re.sub(r'%DVIPSSource:  TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d',
433                            r'%DVIPSSource:  TeX output 1970.01.01:0000', d)
434                 d = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
435                            r'/\1 /XXXXXX', d)
436                 r.append(d)
437                 x = e
438             r.append(s[x:])
439             s = string.join(r, '')
440
441         return s
442
443     def paths(self,patterns):
444         import glob
445         result = []
446         for p in patterns:
447             paths = glob.glob(p)
448             paths.sort()
449             result.extend(paths)
450         return result
451
452
453     def java_ENV(self, version=None):
454         """
455         Initialize with a default external environment that uses a local
456         Java SDK in preference to whatever's found in the default PATH.
457         """
458         try:
459             return self._java_env[version]['ENV']
460         except AttributeError:
461             self._java_env = {}
462         except KeyError:
463             pass
464
465         import SCons.Environment
466         env = SCons.Environment.Environment()
467         self._java_env[version] = env
468
469
470         if version:
471             patterns = [
472                 '/usr/java/jdk%s*/bin'    % version,
473                 '/usr/lib/jvm/*-%s*/bin' % version,
474                 '/usr/local/j2sdk%s*/bin' % version,
475             ]
476             java_path = self.paths(patterns) + [env['ENV']['PATH']]
477         else:
478             patterns = [
479                 '/usr/java/latest/bin',
480                 '/usr/lib/jvm/*/bin',
481                 '/usr/local/j2sdk*/bin',
482             ]
483             java_path = self.paths(patterns) + [env['ENV']['PATH']]
484
485         env['ENV']['PATH'] = string.join(java_path, os.pathsep)
486         return env['ENV']
487
488     def java_where_includes(self,version=None):
489         """
490         Return java include paths compiling java jni code
491         """
492         import glob
493         import sys
494         if not version:
495             version=''
496         jni_dirs = ['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
497                     '/usr/java/jdk%s*/include/jni.h'%version,
498                     ]
499         dirs = self.paths(jni_dirs)
500         if not dirs:
501             return None
502         d=os.path.dirname(self.paths(jni_dirs)[0])
503         result=[d]
504
505         if sys.platform == 'win32':
506             result.append(os.path.join(d,'win32'))
507         elif sys.platform == 'linux2':
508             result.append(os.path.join(d,'linux'))
509         return result
510
511
512     def java_where_java_home(self,version=None):
513         import os.path
514         jar=self.java_where_jar(version)
515         home=os.path.normpath('%s/..'%jar)
516         return home
517
518     def java_where_jar(self, version=None):
519         ENV = self.java_ENV(version)
520         if self.detect_tool('jar', ENV=ENV):
521             where_jar = self.detect('JAR', 'jar', ENV=ENV)
522         else:
523             where_jar = self.where_is('jar', ENV['PATH'])
524         if not where_jar:
525             self.skip_test("Could not find Java jar, skipping test(s).\n")
526         return where_jar
527
528     def java_where_java(self, version=None):
529         """
530         Return a path to the java executable.
531         """
532         ENV = self.java_ENV(version)
533         where_java = self.where_is('java', ENV['PATH'])
534         if not where_java:
535             self.skip_test("Could not find Java java, skipping test(s).\n")
536         return where_java
537
538     def java_where_javac(self, version=None):
539         """
540         Return a path to the javac compiler.
541         """
542         ENV = self.java_ENV(version)
543         if self.detect_tool('javac'):
544             where_javac = self.detect('JAVAC', 'javac', ENV=ENV)
545         else:
546             where_javac = self.where_is('javac', ENV['PATH'])
547         if not where_javac:
548             self.skip_test("Could not find Java javac, skipping test(s).\n")
549         self.run(program = where_javac,
550                  arguments = '-version',
551                  stderr=None,
552                  status=None)
553         if version:
554             if string.find(self.stderr(), 'javac %s' % version) == -1:
555                 fmt = "Could not find javac for Java version %s, skipping test(s).\n"
556                 self.skip_test(fmt % version)
557         else:
558             m = re.search(r'javac (\d\.\d)', self.stderr())
559             if m:
560                 version = m.group(1)
561             else:
562                 version = None
563         return where_javac, version
564
565     def java_where_javah(self, version=None):
566         ENV = self.java_ENV(version)
567         if self.detect_tool('javah'):
568             where_javah = self.detect('JAVAH', 'javah', ENV=ENV)
569         else:
570             where_javah = self.where_is('javah', ENV['PATH'])
571         if not where_javah:
572             self.skip_test("Could not find Java javah, skipping test(s).\n")
573         return where_javah
574
575     def java_where_rmic(self, version=None):
576         ENV = self.java_ENV(version)
577         if self.detect_tool('rmic'):
578             where_rmic = self.detect('RMIC', 'rmic', ENV=ENV)
579         else:
580             where_rmic = self.where_is('rmic', ENV['PATH'])
581         if not where_rmic:
582             self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
583         return where_rmic
584
585     def Qt_dummy_installation(self, dir='qt'):
586         # create a dummy qt installation
587
588         self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] )
589
590         self.write([dir, 'bin', 'mymoc.py'], """\
591 import getopt
592 import sys
593 import string
594 import re
595 cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
596 output = None
597 impl = 0
598 opt_string = ''
599 for opt, arg in cmd_opts:
600     if opt == '-o': output = open(arg, 'wb')
601     elif opt == '-i': impl = 1
602     else: opt_string = opt_string + ' ' + opt
603 for a in args:
604     contents = open(a, 'rb').read()
605     a = string.replace(a, '\\\\', '\\\\\\\\')
606     subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
607     if impl:
608         contents = re.sub( r'#include.*', '', contents )
609     output.write(string.replace(contents, 'Q_OBJECT', subst))
610 output.close()
611 sys.exit(0)
612 """)
613
614         self.write([dir, 'bin', 'myuic.py'], """\
615 import os.path
616 import re
617 import sys
618 import string
619 output_arg = 0
620 impl_arg = 0
621 impl = None
622 source = None
623 for arg in sys.argv[1:]:
624     if output_arg:
625         output = open(arg, 'wb')
626         output_arg = 0
627     elif impl_arg:
628         impl = arg
629         impl_arg = 0
630     elif arg == "-o":
631         output_arg = 1
632     elif arg == "-impl":
633         impl_arg = 1
634     else:
635         if source:
636             sys.exit(1)
637         source = open(arg, 'rb')
638         sourceFile = arg
639 if impl:
640     output.write( '#include "' + impl + '"\\n' )
641     includes = re.findall('<include.*?>(.*?)</include>', source.read())
642     for incFile in includes:
643         # this is valid for ui.h files, at least
644         if os.path.exists(incFile):
645             output.write('#include "' + incFile + '"\\n')
646 else:
647     output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
648 output.close()
649 sys.exit(0)
650 """ )
651
652         self.write([dir, 'include', 'my_qobject.h'], r"""
653 #define Q_OBJECT ;
654 void my_qt_symbol(const char *arg);
655 """)
656
657         self.write([dir, 'lib', 'my_qobject.cpp'], r"""
658 #include "../include/my_qobject.h"
659 #include <stdio.h>
660 void my_qt_symbol(const char *arg) {
661   printf( arg );
662 }
663 """)
664
665         self.write([dir, 'lib', 'SConstruct'], r"""
666 env = Environment()
667 import sys
668 if sys.platform == 'win32':
669     env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
670 else:
671     env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
672 """)
673
674         self.run(chdir = self.workpath(dir, 'lib'),
675                  arguments = '.',
676                  stderr = noisy_ar,
677                  match = self.match_re_dotall)
678
679         self.QT = self.workpath(dir)
680         self.QT_LIB = 'myqt'
681         self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py'))
682         self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py'))
683         self.QT_LIB_DIR = self.workpath(dir, 'lib')
684
685     def Qt_create_SConstruct(self, place):
686         if type(place) is type([]):
687             place = apply(test.workpath, place)
688         self.write(place, """\
689 if ARGUMENTS.get('noqtdir', 0): QTDIR=None
690 else: QTDIR=r'%s'
691 env = Environment(QTDIR = QTDIR,
692                   QT_LIB = r'%s',
693                   QT_MOC = r'%s',
694                   QT_UIC = r'%s',
695                   tools=['default','qt'])
696 dup = 1
697 if ARGUMENTS.get('variant_dir', 0):
698     if ARGUMENTS.get('chdir', 0):
699         SConscriptChdir(1)
700     else:
701         SConscriptChdir(0)
702     dup=int(ARGUMENTS.get('dup', 1))
703     if dup == 0:
704         builddir = 'build_dup0'
705         env['QT_DEBUG'] = 1
706     else:
707         builddir = 'build'
708     VariantDir(builddir, '.', duplicate=dup)
709     print builddir, dup
710     sconscript = Dir(builddir).File('SConscript')
711 else:
712     sconscript = File('SConscript')
713 Export("env dup")
714 SConscript( sconscript )
715 """ % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC))
716
717     def msvs_versions(self):
718         if not hasattr(self, '_msvs_versions'):
719
720             # Determine the SCons version and the versions of the MSVS
721             # environments installed on the test machine.
722             #
723             # We do this by executing SCons with an SConstruct file
724             # (piped on stdin) that spits out Python assignments that
725             # we can just exec().  We construct the SCons.__"version"__
726             # string in the input here so that the SCons build itself
727             # doesn't fill it in when packaging SCons.
728             input = """\
729 import SCons
730 print "self._scons_version =", repr(SCons.__%s__)
731 env = Environment();
732 print "self._msvs_versions =", str(env['MSVS']['VERSIONS'])
733 """ % 'version'
734         
735             self.run(arguments = '-n -q -Q -f -', stdin = input)
736             exec(self.stdout())
737
738         return self._msvs_versions
739
740     def vcproj_sys_path(self, fname):
741         """
742         """
743         orig = 'sys.path = [ join(sys'
744
745         enginepath = repr(os.path.join(self._cwd, '..', 'engine'))
746         replace = 'sys.path = [ %s, join(sys' % enginepath
747
748         contents = self.read(fname)
749         contents = string.replace(contents, orig, replace)
750         self.write(fname, contents)
751
752     def msvs_substitute(self, input, msvs_ver,
753                         subdir=None, sconscript=None,
754                         python=sys.executable,
755                         project_guid=None):
756         if not hasattr(self, '_msvs_versions'):
757             self.msvs_versions()
758
759         if subdir:
760             workpath = self.workpath(subdir)
761         else:
762             workpath = self.workpath()
763
764         if sconscript is None:
765             sconscript = self.workpath('SConstruct')
766
767         if project_guid is None:
768             project_guid = "{E5466E26-0003-F18B-8F8A-BCD76C86388D}"
769
770         if os.environ.has_key('SCONS_LIB_DIR'):
771             exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % os.environ['SCONS_LIB_DIR']
772         else:
773             exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%s'), join(sys.prefix, 'scons-%s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % (self._scons_version, self._scons_version)
774         exec_script_main_xml = string.replace(exec_script_main, "'", "&apos;")
775
776         result = string.replace(input, r'<WORKPATH>', workpath)
777         result = string.replace(result, r'<PYTHON>', python)
778         result = string.replace(result, r'<SCONSCRIPT>', sconscript)
779         result = string.replace(result, r'<SCONS_SCRIPT_MAIN>', exec_script_main)
780         result = string.replace(result, r'<SCONS_SCRIPT_MAIN_XML>', exec_script_main_xml)
781         result = string.replace(result, r'<PROJECT_GUID>', project_guid)
782         return result
783
784     def get_msvs_executable(self, version):
785         """Returns a full path to the executable (MSDEV or devenv)
786         for the specified version of Visual Studio.
787         """
788         common_msdev98_bin_msdev_com = ['Common', 'MSDev98', 'Bin', 'MSDEV.COM']
789         common7_ide_devenv_com       = ['Common7', 'IDE', 'devenv.com']
790         common7_ide_vcexpress_exe    = ['Common7', 'IDE', 'VCExpress.exe']
791         sub_paths = {
792             '6.0' : [
793                 common_msdev98_bin_msdev_com,
794             ],
795             '7.0' : [
796                 common7_ide_devenv_com,
797             ],
798             '7.1' : [
799                 common7_ide_devenv_com,
800             ],
801             '8.0' : [
802                 common7_ide_devenv_com,
803                 common7_ide_vcexpress_exe,
804             ],
805         }
806         from SCons.Tool.msvs import get_msvs_install_dirs
807         vs_path = get_msvs_install_dirs(version)['VSINSTALLDIR']
808         for sp in sub_paths[version]:
809             p = apply(os.path.join, [vs_path] + sp)
810             if os.path.exists(p):
811                 return p
812         return apply(os.path.join, [vs_path] + sub_paths[version][0])
813
814
815     NCR = 0 # non-cached rebuild
816     CR  = 1 # cached rebuild (up to date)
817     NCF = 2 # non-cached build failure
818     CF  = 3 # cached build failure
819
820     if sys.platform == 'win32':
821         Configure_lib = 'msvcrt'
822     else:
823         Configure_lib = 'm'
824
825     # to use cygwin compilers on cmd.exe -> uncomment following line
826     #Configure_lib = 'm'
827
828     def checkLogAndStdout(self, checks, results, cached,
829                           logfile, sconf_dir, sconstruct,
830                           doCheckLog=1, doCheckStdout=1):
831
832         class NoMatch:
833             def __init__(self, p):
834                 self.pos = p
835
836         def matchPart(log, logfile, lastEnd):
837             m = re.match(log, logfile[lastEnd:])
838             if not m:
839                 raise NoMatch, lastEnd
840             return m.end() + lastEnd
841         try:
842             #print len(os.linesep)
843             ls = os.linesep
844             nols = "("
845             for i in range(len(ls)):
846                 nols = nols + "("
847                 for j in range(i):
848                     nols = nols + ls[j]
849                 nols = nols + "[^" + ls[i] + "])"
850                 if i < len(ls)-1:
851                     nols = nols + "|"
852             nols = nols + ")"
853             lastEnd = 0
854             logfile = self.read(self.workpath(logfile))
855             if (doCheckLog and
856                 string.find( logfile, "scons: warning: The stored build "
857                              "information has an unexpected class." ) >= 0):
858                 self.fail_test()
859             sconf_dir = sconf_dir
860             sconstruct = sconstruct
861
862             log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
863             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
864             log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
865             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
866             rdstr = ""
867             cnt = 0
868             for check,result,cache_desc in zip(checks, results, cached):
869                 log   = re.escape("scons: Configure: " + check) + ls
870                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
871                 log = ""
872                 result_cached = 1
873                 for bld_desc in cache_desc: # each TryXXX
874                     for ext, flag in bld_desc: # each file in TryBuild
875                         file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
876                         if flag == self.NCR:
877                             # rebuild will pass
878                             if ext in ['.c', '.cpp']:
879                                 log=log + re.escape(file + " <-") + ls
880                                 log=log + r"(  \|" + nols + "*" + ls + ")+?"
881                             else:
882                                 log=log + "(" + nols + "*" + ls +")*?"
883                             result_cached = 0
884                         if flag == self.CR:
885                             # up to date
886                             log=log + \
887                                  re.escape("scons: Configure: \"%s\" is up to date." 
888                                            % file) + ls
889                             log=log+re.escape("scons: Configure: The original builder "
890                                               "output was:") + ls
891                             log=log+r"(  \|.*"+ls+")+"
892                         if flag == self.NCF:
893                             # non-cached rebuild failure
894                             log=log + "(" + nols + "*" + ls + ")*?"
895                             result_cached = 0
896                         if flag == self.CF:
897                             # cached rebuild failure
898                             log=log + \
899                                  re.escape("scons: Configure: Building \"%s\" failed "
900                                            "in a previous run and all its sources are"
901                                            " up to date." % file) + ls
902                             log=log+re.escape("scons: Configure: The original builder "
903                                               "output was:") + ls
904                             log=log+r"(  \|.*"+ls+")+"
905                     cnt = cnt + 1
906                 if result_cached:
907                     result = "(cached) " + result
908                 rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
909                 log=log + re.escape("scons: Configure: " + result) + ls + ls
910                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
911                 log = ""
912             if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
913             if doCheckLog and lastEnd != len(logfile):
914                 raise NoMatch, lastEnd
915             
916         except NoMatch, m:
917             print "Cannot match log file against log regexp."
918             print "log file: "
919             print "------------------------------------------------------"
920             print logfile[m.pos:]
921             print "------------------------------------------------------"
922             print "log regexp: "
923             print "------------------------------------------------------"
924             print log
925             print "------------------------------------------------------"
926             self.fail_test()
927
928         if doCheckStdout:
929             exp_stdout = self.wrap_stdout(".*", rdstr)
930             if not self.match_re_dotall(self.stdout(), exp_stdout):
931                 print "Unexpected stdout: "
932                 print "-----------------------------------------------------"
933                 print repr(self.stdout())
934                 print "-----------------------------------------------------"
935                 print repr(exp_stdout)
936                 print "-----------------------------------------------------"
937                 self.fail_test()
938
939     def get_python_version(self):
940         """
941         Returns the Python version (just so everyone doesn't have to
942         hand-code slicing the right number of characters).
943         """
944         # see also sys.prefix documentation
945         return python_minor_version_string()
946
947     def get_platform_python(self):
948         """
949         Returns a path to a Python executable suitable for testing on
950         this platform.
951
952         Mac OS X has no static libpython for SWIG to link against,
953         so we have to link against Apple's framwork version.  However,
954         testing must use the executable version that corresponds to the
955         framework we link against, or else we get interpreter errors.
956         """
957         if sys.platform == 'darwin':
958             return '/System/Library/Frameworks/Python.framework/Versions/Current/bin/python'
959         else:
960             global python
961             return python
962
963     def get_quoted_platform_python(self):
964         """
965         Returns a quoted path to a Python executable suitable for testing on
966         this platform.
967
968         Mac OS X has no static libpython for SWIG to link against,
969         so we have to link against Apple's framwork version.  However,
970         testing must use the executable version that corresponds to the
971         framework we link against, or else we get interpreter errors.
972         """
973         if sys.platform == 'darwin':
974             return '"' + self.get_platform_python() + '"'
975         else:
976             global _python_
977             return _python_
978
979     def get_platform_sys_prefix(self):
980         """
981         Returns a "sys.prefix" value suitable for linking on this platform.
982
983         Mac OS X has a built-in Python but no static libpython,
984         so we must link to it using Apple's 'framework' scheme.
985         """
986         if sys.platform == 'darwin':
987             fmt = '/System/Library/Frameworks/Python.framework/Versions/%s/'
988             return fmt % self.get_python_version()
989         else:
990             return sys.prefix
991
992     def get_python_frameworks_flags(self):
993         """
994         Returns a FRAMEWORKSFLAGS value for linking with Python.
995
996         Mac OS X has a built-in Python but no static libpython,
997         so we must link to it using Apple's 'framework' scheme.
998         """
999         if sys.platform == 'darwin':
1000             return '-framework Python'
1001         else:
1002             return ''
1003
1004     def get_python_inc(self):
1005         """
1006         Returns a path to the Python include directory.
1007         """
1008         try:
1009             import distutils.sysconfig
1010         except ImportError:
1011             return os.path.join(self.get_platform_sys_prefix(),
1012                                 'include',
1013                                 'python' + self.get_python_version())
1014         else:
1015             return distutils.sysconfig.get_python_inc()
1016
1017     def wait_for(self, fname, timeout=10.0, popen=None):
1018         """
1019         Waits for the specified file name to exist.
1020         """
1021         waited = 0.0
1022         while not os.path.exists(fname):
1023             if timeout and waited >= timeout:
1024                 sys.stderr.write('timed out waiting for %s to exist\n' % fname)
1025                 if popen:
1026                     popen.stdin.close()
1027                     self.status = 1
1028                     self.finish(popen)
1029                 self.fail_test()
1030             time.sleep(1.0)
1031             waited = waited + 1.0
1032
1033     def get_alt_cpp_suffix(self):
1034         """
1035         Many CXX tests have this same logic.
1036         They all needed to determine if the current os supports
1037         files with .C and .c as different files or not
1038         in which case they are instructed to use .cpp instead of .C
1039         """
1040         if not case_sensitive_suffixes('.c','.C'):
1041             alt_cpp_suffix = '.cpp'
1042         else:
1043             alt_cpp_suffix = '.C'
1044         return alt_cpp_suffix
1045     
1046
1047 # In some environments, $AR will generate a warning message to stderr
1048 # if the library doesn't previously exist and is being created.  One
1049 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
1050 # but this is difficult to do in a platform-/implementation-specific
1051 # method.  Instead, we will use the following as a stderr match for
1052 # tests that use AR so that we will view zero or more "ar: creating
1053 # <file>" messages to be successful executions of the test (see
1054 # test/AR.py for sample usage).
1055
1056 noisy_ar=r'(ar: creating( archive)? \S+\n?)*'