Merged revisions 2949-2953,2955-3056 via svnmerge from
[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.5'
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         stdout = self.wrap_stdout(read_str = read_str, build_str = s)
342         kw['stdout'] = re.escape(stdout)
343         kw['match'] = self.match_re_dotall
344         apply(self.run, [], kw)
345
346     def not_up_to_date(self, options = None, arguments = None, **kw):
347         """Asserts that none of the targets listed in arguments is
348         up to date, but does not make any assumptions on other targets.
349         This function is most useful in conjunction with the -n option.
350         """
351         s = ""
352         for arg in string.split(arguments):
353             s = s + "(?!scons: `%s' is up to date.)" % re.escape(arg)
354             if options:
355                 arguments = options + " " + arguments
356         s = '('+s+'[^\n]*\n)*'
357         kw['arguments'] = arguments
358         stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE'))
359         kw['stdout'] = string.replace(stdout, 'ARGUMENTSGOHERE', s)
360         kw['match'] = self.match_re_dotall
361         apply(self.run, [], kw)
362
363     def diff_substr(self, expect, actual, prelen=20, postlen=40):
364         i = 0
365         for x, y in zip(expect, actual):
366             if x != y:
367                 return "Actual did not match expect at char %d:\n" \
368                        "    Expect:  %s\n" \
369                        "    Actual:  %s\n" \
370                        % (i, repr(expect[i-prelen:i+postlen]),
371                              repr(actual[i-prelen:i+postlen]))
372             i = i + 1
373         return "Actual matched the expected output???"
374
375     def python_file_line(self, file, line):
376         """
377         Returns a Python error line for output comparisons.
378
379         The exec of the traceback line gives us the correct format for
380         this version of Python.  Before 2.5, this yielded:
381
382             File "<string>", line 1, ?
383
384         Python 2.5 changed this to:
385
386             File "<string>", line 1, <module>
387
388         We stick the requested file name and line number in the right
389         places, abstracting out the version difference.
390         """
391         exec 'import traceback; x = traceback.format_stack()[-1]'
392         x = string.lstrip(x)
393         x = string.replace(x, '<string>', file)
394         x = string.replace(x, 'line 1,', 'line %s,' % line)
395         return x
396
397     def normalize_pdf(self, s):
398         s = re.sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
399                    r'/\1Date (D:XXXX)', s)
400         s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
401                    r'/ID [<XXXX> <XXXX>]', s)
402         s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
403                    r'/\1 /XXXXXX', s)
404         s = re.sub(r'/Length \d+ *\n/Filter /FlateDecode\n',
405                    r'/Length XXXX\n/Filter /FlateDecode\n', s)
406
407
408         try:
409             import zlib
410         except ImportError:
411             pass
412         else:
413             begin_marker = '/FlateDecode\n>>\nstream\n'
414             end_marker = 'endstream\nendobj'
415
416             encoded = []
417             b = string.find(s, begin_marker, 0)
418             while b != -1:
419                 b = b + len(begin_marker)
420                 e = string.find(s, end_marker, b)
421                 encoded.append((b, e))
422                 b = string.find(s, begin_marker, e + len(end_marker))
423
424             x = 0
425             r = []
426             for b, e in encoded:
427                 r.append(s[x:b])
428                 d = zlib.decompress(s[b:e])
429                 d = re.sub(r'%%CreationDate: [^\n]*\n',
430                            r'%%CreationDate: 1970 Jan 01 00:00:00\n', d)
431                 d = re.sub(r'%DVIPSSource:  TeX output \d\d\d\d\.\d\d\.\d\d:\d\d\d\d',
432                            r'%DVIPSSource:  TeX output 1970.01.01:0000', d)
433                 d = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
434                            r'/\1 /XXXXXX', d)
435                 r.append(d)
436                 x = e
437             r.append(s[x:])
438             s = string.join(r, '')
439
440         return s
441
442     def paths(self,patterns):
443         import glob
444         result = []
445         for p in patterns:
446             paths = glob.glob(p)
447             paths.sort()
448             result.extend(paths)
449         return result
450
451
452     def java_ENV(self, version=None):
453         """
454         Initialize with a default external environment that uses a local
455         Java SDK in preference to whatever's found in the default PATH.
456         """
457         try:
458             return self._java_env[version]['ENV']
459         except AttributeError:
460             self._java_env = {}
461         except KeyError:
462             pass
463
464         import SCons.Environment
465         env = SCons.Environment.Environment()
466         self._java_env[version] = env
467
468
469         if version:
470             patterns = [
471                 '/usr/java/jdk%s*/bin'    % version,
472                 '/usr/lib/jvm/*-%s*/bin' % version,
473                 '/usr/local/j2sdk%s*/bin' % version,
474             ]
475             java_path = self.paths(patterns) + [env['ENV']['PATH']]
476         else:
477             patterns = [
478                 '/usr/java/latest/bin',
479                 '/usr/lib/jvm/*/bin',
480                 '/usr/local/j2sdk*/bin',
481             ]
482             java_path = self.paths(patterns) + [env['ENV']['PATH']]
483
484         env['ENV']['PATH'] = string.join(java_path, os.pathsep)
485         return env['ENV']
486
487     def java_where_includes(self,version=None):
488         """
489         Return java include paths compiling java jni code
490         """
491         import glob
492         import sys
493         if not version:
494             version=''
495         jni_dirs = ['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
496                     '/usr/java/jdk%s*/include/jni.h'%version,
497                     ]
498         dirs = self.paths(jni_dirs)
499         if not dirs:
500             return None
501         d=os.path.dirname(self.paths(jni_dirs)[0])
502         result=[d]
503
504         if sys.platform == 'win32':
505             result.append(os.path.join(d,'win32'))
506         elif sys.platform == 'linux2':
507             result.append(os.path.join(d,'linux'))
508         return result
509
510
511     def java_where_java_home(self,version=None):
512         import os.path
513         jar=self.java_where_jar(version)
514         home=os.path.normpath('%s/..'%jar)
515         return home
516
517     def java_where_jar(self, version=None):
518         ENV = self.java_ENV(version)
519         if self.detect_tool('jar', ENV=ENV):
520             where_jar = self.detect('JAR', 'jar', ENV=ENV)
521         else:
522             where_jar = self.where_is('jar', ENV['PATH'])
523         if not where_jar:
524             self.skip_test("Could not find Java jar, skipping test(s).\n")
525         return where_jar
526
527     def java_where_java(self, version=None):
528         """
529         Return a path to the java executable.
530         """
531         ENV = self.java_ENV(version)
532         where_java = self.where_is('java', ENV['PATH'])
533         if not where_java:
534             self.skip_test("Could not find Java java, skipping test(s).\n")
535         return where_java
536
537     def java_where_javac(self, version=None):
538         """
539         Return a path to the javac compiler.
540         """
541         ENV = self.java_ENV(version)
542         if self.detect_tool('javac'):
543             where_javac = self.detect('JAVAC', 'javac', ENV=ENV)
544         else:
545             where_javac = self.where_is('javac', ENV['PATH'])
546         if not where_javac:
547             self.skip_test("Could not find Java javac, skipping test(s).\n")
548         self.run(program = where_javac,
549                  arguments = '-version',
550                  stderr=None,
551                  status=None)
552         if version:
553             if string.find(self.stderr(), 'javac %s' % version) == -1:
554                 fmt = "Could not find javac for Java version %s, skipping test(s).\n"
555                 self.skip_test(fmt % version)
556         else:
557             m = re.search(r'javac (\d\.\d)', self.stderr())
558             if m:
559                 version = m.group(1)
560             else:
561                 version = None
562         return where_javac, version
563
564     def java_where_javah(self, version=None):
565         ENV = self.java_ENV(version)
566         if self.detect_tool('javah'):
567             where_javah = self.detect('JAVAH', 'javah', ENV=ENV)
568         else:
569             where_javah = self.where_is('javah', ENV['PATH'])
570         if not where_javah:
571             self.skip_test("Could not find Java javah, skipping test(s).\n")
572         return where_javah
573
574     def java_where_rmic(self, version=None):
575         ENV = self.java_ENV(version)
576         if self.detect_tool('rmic'):
577             where_rmic = self.detect('RMIC', 'rmic', ENV=ENV)
578         else:
579             where_rmic = self.where_is('rmic', ENV['PATH'])
580         if not where_rmic:
581             self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
582         return where_rmic
583
584     def Qt_dummy_installation(self, dir='qt'):
585         # create a dummy qt installation
586
587         self.subdir( dir, [dir, 'bin'], [dir, 'include'], [dir, 'lib'] )
588
589         self.write([dir, 'bin', 'mymoc.py'], """\
590 import getopt
591 import sys
592 import string
593 import re
594 cmd_opts, args = getopt.getopt(sys.argv[1:], 'io:', [])
595 output = None
596 impl = 0
597 opt_string = ''
598 for opt, arg in cmd_opts:
599     if opt == '-o': output = open(arg, 'wb')
600     elif opt == '-i': impl = 1
601     else: opt_string = opt_string + ' ' + opt
602 for a in args:
603     contents = open(a, 'rb').read()
604     a = string.replace(a, '\\\\', '\\\\\\\\')
605     subst = r'{ my_qt_symbol( "' + a + '\\\\n" ); }'
606     if impl:
607         contents = re.sub( r'#include.*', '', contents )
608     output.write(string.replace(contents, 'Q_OBJECT', subst))
609 output.close()
610 sys.exit(0)
611 """)
612
613         self.write([dir, 'bin', 'myuic.py'], """\
614 import os.path
615 import re
616 import sys
617 import string
618 output_arg = 0
619 impl_arg = 0
620 impl = None
621 source = None
622 for arg in sys.argv[1:]:
623     if output_arg:
624         output = open(arg, 'wb')
625         output_arg = 0
626     elif impl_arg:
627         impl = arg
628         impl_arg = 0
629     elif arg == "-o":
630         output_arg = 1
631     elif arg == "-impl":
632         impl_arg = 1
633     else:
634         if source:
635             sys.exit(1)
636         source = open(arg, 'rb')
637         sourceFile = arg
638 if impl:
639     output.write( '#include "' + impl + '"\\n' )
640     includes = re.findall('<include.*?>(.*?)</include>', source.read())
641     for incFile in includes:
642         # this is valid for ui.h files, at least
643         if os.path.exists(incFile):
644             output.write('#include "' + incFile + '"\\n')
645 else:
646     output.write( '#include "my_qobject.h"\\n' + source.read() + " Q_OBJECT \\n" )
647 output.close()
648 sys.exit(0)
649 """ )
650
651         self.write([dir, 'include', 'my_qobject.h'], r"""
652 #define Q_OBJECT ;
653 void my_qt_symbol(const char *arg);
654 """)
655
656         self.write([dir, 'lib', 'my_qobject.cpp'], r"""
657 #include "../include/my_qobject.h"
658 #include <stdio.h>
659 void my_qt_symbol(const char *arg) {
660   printf( arg );
661 }
662 """)
663
664         self.write([dir, 'lib', 'SConstruct'], r"""
665 env = Environment()
666 import sys
667 if sys.platform == 'win32':
668     env.StaticLibrary( 'myqt', 'my_qobject.cpp' )
669 else:
670     env.SharedLibrary( 'myqt', 'my_qobject.cpp' )
671 """)
672
673         self.run(chdir = self.workpath(dir, 'lib'),
674                  arguments = '.',
675                  stderr = noisy_ar,
676                  match = self.match_re_dotall)
677
678         self.QT = self.workpath(dir)
679         self.QT_LIB = 'myqt'
680         self.QT_MOC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'mymoc.py'))
681         self.QT_UIC = '%s %s' % (_python_, self.workpath(dir, 'bin', 'myuic.py'))
682         self.QT_LIB_DIR = self.workpath(dir, 'lib')
683
684     def Qt_create_SConstruct(self, place):
685         if type(place) is type([]):
686             place = apply(test.workpath, place)
687         self.write(place, """\
688 if ARGUMENTS.get('noqtdir', 0): QTDIR=None
689 else: QTDIR=r'%s'
690 env = Environment(QTDIR = QTDIR,
691                   QT_LIB = r'%s',
692                   QT_MOC = r'%s',
693                   QT_UIC = r'%s',
694                   tools=['default','qt'])
695 dup = 1
696 if ARGUMENTS.get('variant_dir', 0):
697     if ARGUMENTS.get('chdir', 0):
698         SConscriptChdir(1)
699     else:
700         SConscriptChdir(0)
701     dup=int(ARGUMENTS.get('dup', 1))
702     if dup == 0:
703         builddir = 'build_dup0'
704         env['QT_DEBUG'] = 1
705     else:
706         builddir = 'build'
707     VariantDir(builddir, '.', duplicate=dup)
708     print builddir, dup
709     sconscript = Dir(builddir).File('SConscript')
710 else:
711     sconscript = File('SConscript')
712 Export("env dup")
713 SConscript( sconscript )
714 """ % (self.QT, self.QT_LIB, self.QT_MOC, self.QT_UIC))
715
716     def msvs_versions(self):
717         if not hasattr(self, '_msvs_versions'):
718
719             # Determine the SCons version and the versions of the MSVS
720             # environments installed on the test machine.
721             #
722             # We do this by executing SCons with an SConstruct file
723             # (piped on stdin) that spits out Python assignments that
724             # we can just exec().  We construct the SCons.__"version"__
725             # string in the input here so that the SCons build itself
726             # doesn't fill it in when packaging SCons.
727             input = """\
728 import SCons
729 print "self._scons_version =", repr(SCons.__%s__)
730 env = Environment();
731 print "self._msvs_versions =", str(env['MSVS']['VERSIONS'])
732 """ % 'version'
733         
734             self.run(arguments = '-n -q -Q -f -', stdin = input)
735             exec(self.stdout())
736
737         return self._msvs_versions
738
739     def vcproj_sys_path(self, fname):
740         """
741         """
742         orig = 'sys.path = [ join(sys'
743
744         enginepath = repr(os.path.join(self._cwd, '..', 'engine'))
745         replace = 'sys.path = [ %s, join(sys' % enginepath
746
747         contents = self.read(fname)
748         contents = string.replace(contents, orig, replace)
749         self.write(fname, contents)
750
751     def msvs_substitute(self, input, msvs_ver,
752                         subdir=None, sconscript=None,
753                         python=sys.executable,
754                         project_guid=None):
755         if not hasattr(self, '_msvs_versions'):
756             self.msvs_versions()
757
758         if subdir:
759             workpath = self.workpath(subdir)
760         else:
761             workpath = self.workpath()
762
763         if sconscript is None:
764             sconscript = self.workpath('SConstruct')
765
766         if project_guid is None:
767             project_guid = "{E5466E26-0003-F18B-8F8A-BCD76C86388D}"
768
769         if os.environ.has_key('SCONS_LIB_DIR'):
770             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']
771         else:
772             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)
773         exec_script_main_xml = string.replace(exec_script_main, "'", "&apos;")
774
775         result = string.replace(input, r'<WORKPATH>', workpath)
776         result = string.replace(result, r'<PYTHON>', python)
777         result = string.replace(result, r'<SCONSCRIPT>', sconscript)
778         result = string.replace(result, r'<SCONS_SCRIPT_MAIN>', exec_script_main)
779         result = string.replace(result, r'<SCONS_SCRIPT_MAIN_XML>', exec_script_main_xml)
780         result = string.replace(result, r'<PROJECT_GUID>', project_guid)
781         return result
782
783     def get_msvs_executable(self, version):
784         """Returns a full path to the executable (MSDEV or devenv)
785         for the specified version of Visual Studio.
786         """
787         common_msdev98_bin_msdev_com = ['Common', 'MSDev98', 'Bin', 'MSDEV.COM']
788         common7_ide_devenv_com       = ['Common7', 'IDE', 'devenv.com']
789         common7_ide_vcexpress_exe    = ['Common7', 'IDE', 'VCExpress.exe']
790         sub_paths = {
791             '6.0' : [
792                 common_msdev98_bin_msdev_com,
793             ],
794             '7.0' : [
795                 common7_ide_devenv_com,
796             ],
797             '7.1' : [
798                 common7_ide_devenv_com,
799             ],
800             '8.0' : [
801                 common7_ide_devenv_com,
802                 common7_ide_vcexpress_exe,
803             ],
804         }
805         from SCons.Tool.msvs import get_msvs_install_dirs
806         vs_path = get_msvs_install_dirs(version)['VSINSTALLDIR']
807         for sp in sub_paths[version]:
808             p = apply(os.path.join, [vs_path] + sp)
809             if os.path.exists(p):
810                 return p
811         return apply(os.path.join, [vs_path] + sub_paths[version][0])
812
813
814     NCR = 0 # non-cached rebuild
815     CR  = 1 # cached rebuild (up to date)
816     NCF = 2 # non-cached build failure
817     CF  = 3 # cached build failure
818
819     if sys.platform == 'win32':
820         Configure_lib = 'msvcrt'
821     else:
822         Configure_lib = 'm'
823
824     # to use cygwin compilers on cmd.exe -> uncomment following line
825     #Configure_lib = 'm'
826
827     def checkLogAndStdout(self, checks, results, cached,
828                           logfile, sconf_dir, sconstruct,
829                           doCheckLog=1, doCheckStdout=1):
830
831         class NoMatch:
832             def __init__(self, p):
833                 self.pos = p
834
835         def matchPart(log, logfile, lastEnd, NoMatch=NoMatch):
836             m = re.match(log, logfile[lastEnd:])
837             if not m:
838                 raise NoMatch, lastEnd
839             return m.end() + lastEnd
840         try:
841             #print len(os.linesep)
842             ls = os.linesep
843             nols = "("
844             for i in range(len(ls)):
845                 nols = nols + "("
846                 for j in range(i):
847                     nols = nols + ls[j]
848                 nols = nols + "[^" + ls[i] + "])"
849                 if i < len(ls)-1:
850                     nols = nols + "|"
851             nols = nols + ")"
852             lastEnd = 0
853             logfile = self.read(self.workpath(logfile))
854             if (doCheckLog and
855                 string.find( logfile, "scons: warning: The stored build "
856                              "information has an unexpected class." ) >= 0):
857                 self.fail_test()
858             sconf_dir = sconf_dir
859             sconstruct = sconstruct
860
861             log = r'file\ \S*%s\,line \d+:' % re.escape(sconstruct) + ls
862             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
863             log = "\t" + re.escape("Configure(confdir = %s)" % sconf_dir) + ls
864             if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
865             rdstr = ""
866             cnt = 0
867             for check,result,cache_desc in zip(checks, results, cached):
868                 log   = re.escape("scons: Configure: " + check) + ls
869                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
870                 log = ""
871                 result_cached = 1
872                 for bld_desc in cache_desc: # each TryXXX
873                     for ext, flag in bld_desc: # each file in TryBuild
874                         file = os.path.join(sconf_dir,"conftest_%d%s" % (cnt, ext))
875                         if flag == self.NCR:
876                             # rebuild will pass
877                             if ext in ['.c', '.cpp']:
878                                 log=log + re.escape(file + " <-") + ls
879                                 log=log + r"(  \|" + nols + "*" + ls + ")+?"
880                             else:
881                                 log=log + "(" + nols + "*" + ls +")*?"
882                             result_cached = 0
883                         if flag == self.CR:
884                             # up to date
885                             log=log + \
886                                  re.escape("scons: Configure: \"%s\" is up to date." 
887                                            % file) + ls
888                             log=log+re.escape("scons: Configure: The original builder "
889                                               "output was:") + ls
890                             log=log+r"(  \|.*"+ls+")+"
891                         if flag == self.NCF:
892                             # non-cached rebuild failure
893                             log=log + "(" + nols + "*" + ls + ")*?"
894                             result_cached = 0
895                         if flag == self.CF:
896                             # cached rebuild failure
897                             log=log + \
898                                  re.escape("scons: Configure: Building \"%s\" failed "
899                                            "in a previous run and all its sources are"
900                                            " up to date." % file) + ls
901                             log=log+re.escape("scons: Configure: The original builder "
902                                               "output was:") + ls
903                             log=log+r"(  \|.*"+ls+")+"
904                     cnt = cnt + 1
905                 if result_cached:
906                     result = "(cached) " + result
907                 rdstr = rdstr + re.escape(check) + re.escape(result) + "\n"
908                 log=log + re.escape("scons: Configure: " + result) + ls + ls
909                 if doCheckLog: lastEnd = matchPart(log, logfile, lastEnd)
910                 log = ""
911             if doCheckLog: lastEnd = matchPart(ls, logfile, lastEnd)
912             if doCheckLog and lastEnd != len(logfile):
913                 raise NoMatch, lastEnd
914             
915         except NoMatch, m:
916             print "Cannot match log file against log regexp."
917             print "log file: "
918             print "------------------------------------------------------"
919             print logfile[m.pos:]
920             print "------------------------------------------------------"
921             print "log regexp: "
922             print "------------------------------------------------------"
923             print log
924             print "------------------------------------------------------"
925             self.fail_test()
926
927         if doCheckStdout:
928             exp_stdout = self.wrap_stdout(".*", rdstr)
929             if not self.match_re_dotall(self.stdout(), exp_stdout):
930                 print "Unexpected stdout: "
931                 print "-----------------------------------------------------"
932                 print repr(self.stdout())
933                 print "-----------------------------------------------------"
934                 print repr(exp_stdout)
935                 print "-----------------------------------------------------"
936                 self.fail_test()
937
938     def get_python_version(self):
939         """
940         Returns the Python version (just so everyone doesn't have to
941         hand-code slicing the right number of characters).
942         """
943         # see also sys.prefix documentation
944         return python_minor_version_string()
945
946     def get_platform_python(self):
947         """
948         Returns a path to a Python executable suitable for testing on
949         this platform.
950
951         Mac OS X has no static libpython for SWIG to link against,
952         so we have to link against Apple's framwork version.  However,
953         testing must use the executable version that corresponds to the
954         framework we link against, or else we get interpreter errors.
955         """
956         if sys.platform == 'darwin':
957             return '/System/Library/Frameworks/Python.framework/Versions/Current/bin/python'
958         else:
959             global python
960             return python
961
962     def get_quoted_platform_python(self):
963         """
964         Returns a quoted path to a Python executable suitable for testing on
965         this platform.
966
967         Mac OS X has no static libpython for SWIG to link against,
968         so we have to link against Apple's framwork version.  However,
969         testing must use the executable version that corresponds to the
970         framework we link against, or else we get interpreter errors.
971         """
972         if sys.platform == 'darwin':
973             return '"' + self.get_platform_python() + '"'
974         else:
975             global _python_
976             return _python_
977
978     def get_platform_sys_prefix(self):
979         """
980         Returns a "sys.prefix" value suitable for linking on this platform.
981
982         Mac OS X has a built-in Python but no static libpython,
983         so we must link to it using Apple's 'framework' scheme.
984         """
985         if sys.platform == 'darwin':
986             fmt = '/System/Library/Frameworks/Python.framework/Versions/%s/'
987             return fmt % self.get_python_version()
988         else:
989             return sys.prefix
990
991     def get_python_frameworks_flags(self):
992         """
993         Returns a FRAMEWORKSFLAGS value for linking with Python.
994
995         Mac OS X has a built-in Python but no static libpython,
996         so we must link to it using Apple's 'framework' scheme.
997         """
998         if sys.platform == 'darwin':
999             return '-framework Python'
1000         else:
1001             return ''
1002
1003     def get_python_inc(self):
1004         """
1005         Returns a path to the Python include directory.
1006         """
1007         try:
1008             import distutils.sysconfig
1009         except ImportError:
1010             return os.path.join(self.get_platform_sys_prefix(),
1011                                 'include',
1012                                 'python' + self.get_python_version())
1013         else:
1014             return distutils.sysconfig.get_python_inc()
1015
1016     def wait_for(self, fname, timeout=10.0, popen=None):
1017         """
1018         Waits for the specified file name to exist.
1019         """
1020         waited = 0.0
1021         while not os.path.exists(fname):
1022             if timeout and waited >= timeout:
1023                 sys.stderr.write('timed out waiting for %s to exist\n' % fname)
1024                 if popen:
1025                     popen.stdin.close()
1026                     self.status = 1
1027                     self.finish(popen)
1028                 self.fail_test()
1029             time.sleep(1.0)
1030             waited = waited + 1.0
1031
1032     def get_alt_cpp_suffix(self):
1033         """
1034         Many CXX tests have this same logic.
1035         They all needed to determine if the current os supports
1036         files with .C and .c as different files or not
1037         in which case they are instructed to use .cpp instead of .C
1038         """
1039         if not case_sensitive_suffixes('.c','.C'):
1040             alt_cpp_suffix = '.cpp'
1041         else:
1042             alt_cpp_suffix = '.C'
1043         return alt_cpp_suffix
1044     
1045
1046 # In some environments, $AR will generate a warning message to stderr
1047 # if the library doesn't previously exist and is being created.  One
1048 # way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
1049 # but this is difficult to do in a platform-/implementation-specific
1050 # method.  Instead, we will use the following as a stderr match for
1051 # tests that use AR so that we will view zero or more "ar: creating
1052 # <file>" messages to be successful executions of the test (see
1053 # test/AR.py for sample usage).
1054
1055 noisy_ar=r'(ar: creating( archive)? \S+\n?)*'