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