summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--QMTest/TestCmd.py427
-rw-r--r--QMTest/TestCommon.py137
-rw-r--r--QMTest/TestSCons.py143
-rw-r--r--QMTest/TestSCons_time.py24
-rw-r--r--doc/man/scons-time.113
-rw-r--r--doc/man/scons.1252
-rw-r--r--doc/scons.mod1
-rw-r--r--src/CHANGES.txt135
-rw-r--r--src/RELEASE.txt37
-rw-r--r--src/engine/MANIFEST.in3
-rw-r--r--src/engine/SCons/Action.py185
-rw-r--r--src/engine/SCons/ActionTests.py21
-rw-r--r--src/engine/SCons/CacheDir.py44
-rw-r--r--src/engine/SCons/Conftest.py78
-rw-r--r--src/engine/SCons/Defaults.py36
-rw-r--r--src/engine/SCons/Environment.py24
-rw-r--r--src/engine/SCons/EnvironmentTests.py54
-rw-r--r--src/engine/SCons/Executor.py10
-rw-r--r--src/engine/SCons/Job.py50
-rw-r--r--src/engine/SCons/JobTests.py2
-rw-r--r--src/engine/SCons/Memoize.py42
-rw-r--r--src/engine/SCons/MemoizeTests.py4
-rw-r--r--src/engine/SCons/Node/FS.py80
-rw-r--r--src/engine/SCons/Node/NodeTests.py14
-rw-r--r--src/engine/SCons/Node/__init__.py16
-rw-r--r--src/engine/SCons/PathList.py9
-rw-r--r--src/engine/SCons/Platform/posix.py2
-rw-r--r--src/engine/SCons/SConf.py41
-rw-r--r--src/engine/SCons/SConfTests.py55
-rw-r--r--src/engine/SCons/Scanner/C.py84
-rw-r--r--src/engine/SCons/Scanner/D.py23
-rw-r--r--src/engine/SCons/Scanner/LaTeX.py2
-rw-r--r--src/engine/SCons/Scanner/ScannerTests.py3
-rw-r--r--src/engine/SCons/Scanner/__init__.py7
-rw-r--r--src/engine/SCons/Script/Interactive.py359
-rw-r--r--src/engine/SCons/Script/Main.py104
-rw-r--r--src/engine/SCons/Script/SConsOptions.py23
-rw-r--r--src/engine/SCons/Subst.py66
-rw-r--r--src/engine/SCons/SubstTests.py26
-rw-r--r--src/engine/SCons/Taskmaster.py44
-rw-r--r--src/engine/SCons/Tool/applelink.py6
-rw-r--r--src/engine/SCons/Tool/gfortran.py62
-rw-r--r--src/engine/SCons/Tool/gfortran.xml15
-rw-r--r--src/engine/SCons/Tool/intelc.py77
-rw-r--r--src/engine/SCons/Tool/jar.py25
-rw-r--r--src/engine/SCons/Tool/jar.xml5
-rw-r--r--src/engine/SCons/Tool/link.py9
-rw-r--r--src/engine/SCons/Tool/mslink.py48
-rw-r--r--src/engine/SCons/Tool/qt.py2
-rw-r--r--src/engine/SCons/Tool/rmic.py6
-rw-r--r--src/engine/SCons/Tool/swig.py3
-rw-r--r--src/engine/SCons/Tool/tex.py98
-rw-r--r--src/engine/SCons/Tool/yacc.py11
-rw-r--r--src/engine/SCons/Tool/yacc.xml8
-rw-r--r--src/engine/SCons/Util.py124
-rw-r--r--src/engine/SCons/UtilTests.py28
-rw-r--r--src/engine/SCons/Warnings.py3
-rw-r--r--src/engine/SCons/compat/__init__.py22
-rw-r--r--src/engine/SCons/compat/_scons_shlex.py319
-rw-r--r--src/engine/SCons/cpp.py73
-rw-r--r--src/engine/SCons/cppTests.py156
-rw-r--r--src/script/scons-time.py70
-rw-r--r--src/test_strings.py4
-rw-r--r--test/Actions/function.py192
-rw-r--r--test/CPPDEFINES/basic.py64
-rw-r--r--test/CPPDEFINES/live.py (renamed from test/CPPDEFINES.py)45
-rw-r--r--test/CPPDEFINES/scan.py181
-rw-r--r--test/CPPDEFINES/undefined.py45
-rw-r--r--test/CPPPATH/absolute-path.py75
-rw-r--r--test/CPPPATH/function-expansion.py137
-rw-r--r--test/CPPPATH/list-expansion.py132
-rw-r--r--test/Configure/Builder-call.py4
-rw-r--r--test/D/DMD.py (renamed from test/DMD.py)0
-rw-r--r--test/D/Scanner.py144
-rw-r--r--test/Errors/SyntaxError.py4
-rw-r--r--test/Errors/execute-a-directory.py109
-rw-r--r--test/Errors/non-executable-file.py103
-rw-r--r--test/Errors/nonexistent-executable.py107
-rw-r--r--test/Errors/permission-denied.py64
-rw-r--r--test/Install/option--install-sandbox.py9
-rw-r--r--test/Interactive/Alias.py85
-rw-r--r--test/Interactive/Default-None.py96
-rw-r--r--test/Interactive/Default.py86
-rw-r--r--test/Interactive/added-include.py118
-rw-r--r--test/Interactive/basic.py84
-rw-r--r--test/Interactive/cache-debug.py117
-rw-r--r--test/Interactive/cache-disable.py115
-rw-r--r--test/Interactive/cache-force.py113
-rw-r--r--test/Interactive/cache-show.py115
-rw-r--r--test/Interactive/clean.py105
-rw-r--r--test/Interactive/exit.py64
-rw-r--r--test/Interactive/help.py85
-rw-r--r--test/Interactive/implicit-BuildDir.py142
-rw-r--r--test/Interactive/option--Q.py83
-rw-r--r--test/Interactive/option-i.py106
-rw-r--r--test/Interactive/option-j.py164
-rw-r--r--test/Interactive/option-k.py108
-rw-r--r--test/Interactive/option-n.py79
-rw-r--r--test/Interactive/option-s.py79
-rw-r--r--test/Interactive/repeat-line.py87
-rw-r--r--test/Interactive/shell.py116
-rw-r--r--test/Interactive/taskmastertrace.py90
-rw-r--r--test/Interactive/tree.py91
-rw-r--r--test/Interactive/unknown-command.py66
-rw-r--r--test/Interactive/version.py84
-rw-r--r--test/Java/JAR.py20
-rw-r--r--test/Java/JARCHDIR.py17
-rw-r--r--test/Java/JARFLAGS.py23
-rw-r--r--test/Java/JAVABOOTCLASSPATH.py17
-rw-r--r--test/Java/JAVACFLAGS.py9
-rw-r--r--test/Java/JAVACLASSPATH.py17
-rw-r--r--test/Java/JAVAH.py25
-rw-r--r--test/Java/JAVASOURCEPATH.py9
-rw-r--r--test/Java/Java-1.4.py33
-rw-r--r--test/Java/Java-1.5.py16
-rw-r--r--test/Java/Java-1.6.py16
-rw-r--r--test/Java/RMIC.py40
-rw-r--r--test/Java/RMICCOM.py6
-rw-r--r--test/Java/RMICCOMSTR.py6
-rw-r--r--test/Java/multi-step.py18
-rw-r--r--test/Java/no-JARCHDIR.py122
-rw-r--r--test/Java/source-files.py9
-rw-r--r--test/Java/swig-dependencies.py29
-rw-r--r--test/Libs/LIBPATH.py (renamed from test/LIBPATH.py)0
-rw-r--r--test/Libs/LIBPREFIX.py (renamed from test/LIBPREFIX.py)0
-rw-r--r--test/Libs/LIBPREFIXES.py (renamed from test/LIBPREFIXES.py)0
-rw-r--r--test/Libs/LIBS.py (renamed from test/LIBS.py)0
-rw-r--r--test/Libs/LIBSUFFIX.py (renamed from test/LIBSUFFIX.py)0
-rw-r--r--test/Libs/LIBSUFFIXES.py (renamed from test/LIBSUFFIXES.py)0
-rw-r--r--test/Libs/Library.py (renamed from test/Library.py)0
-rw-r--r--test/Libs/SHLIBPREFIX.py (renamed from test/SHLIBPREFIX.py)0
-rw-r--r--test/Libs/SHLIBSUFFIX.py (renamed from test/SHLIBSUFFIX.py)0
-rw-r--r--test/Libs/SharedLibrary.py (renamed from test/SharedLibrary.py)0
-rw-r--r--test/Libs/SharedLibraryIxes.py261
-rw-r--r--test/LoadableModule.py3
-rw-r--r--test/MSVC/pdb-manifest.py81
-rw-r--r--test/QT/moc-from-header.py4
-rw-r--r--test/QT/warnings.py7
-rw-r--r--test/Repository/Java.py16
-rw-r--r--test/Repository/JavaH.py22
-rw-r--r--test/Repository/LIBPATH.py7
-rw-r--r--test/Repository/RMIC.py88
-rw-r--r--test/SWIG/module-parens.py102
-rw-r--r--test/Scanner/generated.py8
-rw-r--r--test/TEX/build_dir_dup0.py257
-rw-r--r--test/TEX/multi-run.py88
-rw-r--r--test/YACC/live.py10
-rw-r--r--test/build-errors.py223
-rw-r--r--test/builderrors.py16
-rw-r--r--test/import.py1
-rw-r--r--test/option-k.py168
-rw-r--r--test/option/debug-includes.py9
-rw-r--r--test/option/debug-memoizer.py32
-rw-r--r--test/option/stack-size.py357
-rw-r--r--test/scons-time/func/file.py27
-rw-r--r--test/scons-time/mem/file.py9
-rw-r--r--test/scons-time/obj/file.py9
-rw-r--r--test/scons-time/run/aegis.py17
-rw-r--r--test/scons-time/run/config/archive_list.py6
-rw-r--r--test/scons-time/run/option/quiet.py21
-rw-r--r--test/scons-time/run/option/verbose.py21
-rw-r--r--test/scons-time/run/subversion.py17
-rw-r--r--test/scons-time/time/empty.py91
-rw-r--r--test/scons-time/time/file.py9
-rw-r--r--test/scons-time/time/no-result.py75
-rw-r--r--test/subclassing.py37
-rw-r--r--test/timestamp-fallback.py9
-rw-r--r--timings/CPPPATH/SConstruct66
-rw-r--r--timings/CPPPATH/st.conf44
-rw-r--r--timings/JTimer/SConstruct54
-rw-r--r--timings/JTimer/st.conf48
-rw-r--r--timings/SCons_Bars.py118
-rw-r--r--timings/hundred/SConstruct52
-rw-r--r--timings/hundred/st.conf47
174 files changed, 9401 insertions, 1315 deletions
diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py
index f5e1c715..8bf054b3 100644
--- a/QMTest/TestCmd.py
+++ b/QMTest/TestCmd.py
@@ -181,12 +181,12 @@ version.
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight"
-__version__ = "0.30"
+__revision__ = "TestCmd.py 0.31.D001 2008/01/01 09:05:59 knight"
+__version__ = "0.31"
+import errno
import os
import os.path
-import popen2
import re
import shutil
import stat
@@ -457,6 +457,252 @@ else:
default_sleep_seconds = 1
+
+
+try:
+ import subprocess
+except ImportError:
+ # The subprocess module doesn't exist in this version of Python,
+ # so we're going to cobble up something that looks just enough
+ # like its API for our purposes below.
+ import new
+
+ subprocess = new.module('subprocess')
+
+ subprocess.PIPE = 'PIPE'
+ subprocess.STDOUT = 'STDOUT'
+ subprocess.mswindows = (sys.platform == 'win32')
+
+ try:
+ import popen2
+ popen2.Popen3
+ except AttributeError:
+ class Popen3:
+ universal_newlines = 1
+ def __init__(self, command, **kw):
+ if sys.platform == 'win32' and command[0] == '"':
+ command = '"' + command + '"'
+ (stdin, stdout, stderr) = os.popen3(' ' + command)
+ self.stdin = stdin
+ self.stdout = stdout
+ self.stderr = stderr
+ def close_output(self):
+ self.stdout.close()
+ self.resultcode = self.stderr.close()
+ def wait(self):
+ return self.resultcode
+
+ else:
+ try:
+ popen2.Popen4
+ except AttributeError:
+ # A cribbed Popen4 class, with some retrofitted code from
+ # the Python 1.5 Popen3 class methods to do certain things
+ # by hand.
+ class Popen4(popen2.Popen3):
+ childerr = None
+
+ def __init__(self, cmd, bufsize=-1):
+ p2cread, p2cwrite = os.pipe()
+ c2pread, c2pwrite = os.pipe()
+ self.pid = os.fork()
+ if self.pid == 0:
+ # Child
+ os.dup2(p2cread, 0)
+ os.dup2(c2pwrite, 1)
+ os.dup2(c2pwrite, 2)
+ for i in range(3, popen2.MAXFD):
+ try:
+ os.close(i)
+ except: pass
+ try:
+ os.execvp(cmd[0], cmd)
+ finally:
+ os._exit(1)
+ # Shouldn't come here, I guess
+ os._exit(1)
+ os.close(p2cread)
+ self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
+ os.close(c2pwrite)
+ self.fromchild = os.fdopen(c2pread, 'r', bufsize)
+ popen2._active.append(self)
+
+ popen2.Popen4 = Popen4
+
+ class Popen3(popen2.Popen3, popen2.Popen4):
+ universal_newlines = 1
+ def __init__(self, command, **kw):
+ if kw.get('stderr') == 'STDOUT':
+ apply(popen2.Popen4.__init__, (self, command, 1))
+ else:
+ apply(popen2.Popen3.__init__, (self, command, 1))
+ self.stdin = self.tochild
+ self.stdout = self.fromchild
+ self.stderr = self.childerr
+
+ subprocess.Popen = Popen3
+
+
+
+# From Josiah Carlson,
+# ASPN : Python Cookbook : Module to allow Asynchronous subprocess use on Windows and Posix platforms
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554
+
+PIPE = subprocess.PIPE
+
+if subprocess.mswindows:
+ from win32file import ReadFile, WriteFile
+ from win32pipe import PeekNamedPipe
+ import msvcrt
+else:
+ import select
+ import fcntl
+
+ try: fcntl.F_GETFL
+ except AttributeError: fcntl.F_GETFL = 3
+
+ try: fcntl.F_SETFL
+ except AttributeError: fcntl.F_SETFL = 4
+
+class Popen(subprocess.Popen):
+ def recv(self, maxsize=None):
+ return self._recv('stdout', maxsize)
+
+ def recv_err(self, maxsize=None):
+ return self._recv('stderr', maxsize)
+
+ def send_recv(self, input='', maxsize=None):
+ return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
+
+ def get_conn_maxsize(self, which, maxsize):
+ if maxsize is None:
+ maxsize = 1024
+ elif maxsize < 1:
+ maxsize = 1
+ return getattr(self, which), maxsize
+
+ def _close(self, which):
+ getattr(self, which).close()
+ setattr(self, which, None)
+
+ if subprocess.mswindows:
+ def send(self, input):
+ if not self.stdin:
+ return None
+
+ try:
+ x = msvcrt.get_osfhandle(self.stdin.fileno())
+ (errCode, written) = WriteFile(x, input)
+ except ValueError:
+ return self._close('stdin')
+ except (subprocess.pywintypes.error, Exception), why:
+ if why[0] in (109, errno.ESHUTDOWN):
+ return self._close('stdin')
+ raise
+
+ return written
+
+ def _recv(self, which, maxsize):
+ conn, maxsize = self.get_conn_maxsize(which, maxsize)
+ if conn is None:
+ return None
+
+ try:
+ x = msvcrt.get_osfhandle(conn.fileno())
+ (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
+ if maxsize < nAvail:
+ nAvail = maxsize
+ if nAvail > 0:
+ (errCode, read) = ReadFile(x, nAvail, None)
+ except ValueError:
+ return self._close(which)
+ except (subprocess.pywintypes.error, Exception), why:
+ if why[0] in (109, errno.ESHUTDOWN):
+ return self._close(which)
+ raise
+
+ #if self.universal_newlines:
+ # read = self._translate_newlines(read)
+ return read
+
+ else:
+ def send(self, input):
+ if not self.stdin:
+ return None
+
+ if not select.select([], [self.stdin], [], 0)[1]:
+ return 0
+
+ try:
+ written = os.write(self.stdin.fileno(), input)
+ except OSError, why:
+ if why[0] == errno.EPIPE: #broken pipe
+ return self._close('stdin')
+ raise
+
+ return written
+
+ def _recv(self, which, maxsize):
+ conn, maxsize = self.get_conn_maxsize(which, maxsize)
+ if conn is None:
+ return None
+
+ try:
+ flags = fcntl.fcntl(conn, fcntl.F_GETFL)
+ except TypeError:
+ flags = None
+ else:
+ if not conn.closed:
+ fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+
+ try:
+ if not select.select([conn], [], [], 0)[0]:
+ return ''
+
+ r = conn.read(maxsize)
+ if not r:
+ return self._close(which)
+
+ #if self.universal_newlines:
+ # r = self._translate_newlines(r)
+ return r
+ finally:
+ if not conn.closed and not flags is None:
+ fcntl.fcntl(conn, fcntl.F_SETFL, flags)
+
+disconnect_message = "Other end disconnected!"
+
+def recv_some(p, t=.1, e=1, tr=5, stderr=0):
+ if tr < 1:
+ tr = 1
+ x = time.time()+t
+ y = []
+ r = ''
+ pr = p.recv
+ if stderr:
+ pr = p.recv_err
+ while time.time() < x or r:
+ r = pr()
+ if r is None:
+ if e:
+ raise Exception(disconnect_message)
+ else:
+ break
+ elif r:
+ y.append(r)
+ else:
+ time.sleep(max((x-time.time())/tr, 0))
+ return ''.join(y)
+
+def send_all(p, data):
+ while len(data):
+ sent = p.send(data)
+ if sent is None:
+ raise Exception(disconnect_message)
+ data = buffer(data, sent)
+
+
+
class TestCmd:
"""Class TestCmd
"""
@@ -703,26 +949,17 @@ class TestCmd:
dir = self.canonicalize(dir)
os.rmdir(dir)
- def run(self, program = None,
- interpreter = None,
- arguments = None,
- chdir = None,
- stdin = None,
- universal_newlines = None):
- """Runs a test of the program or script for the test
- environment. Standard output and error output are saved for
- future retrieval via the stdout() and stderr() methods.
+ def start(self, program = None,
+ interpreter = None,
+ arguments = None,
+ universal_newlines = None,
+ **kw):
+ """
+ Starts a program or script for the test environment.
The specified program will have the original directory
- prepending unless it is enclosed in a [list].
+ prepended unless it is enclosed in a [list].
"""
- if chdir:
- oldcwd = os.getcwd()
- if not os.path.isabs(chdir):
- chdir = os.path.join(self.workpath(chdir))
- if self.verbose:
- sys.stderr.write("chdir(" + chdir + ")\n")
- os.chdir(chdir)
if program:
if type(program) == type('') and not os.path.isabs(program):
program = os.path.join(self._cwd, program)
@@ -747,38 +984,56 @@ class TestCmd:
if universal_newlines is None:
universal_newlines = self.universal_newlines
- try:
- import subprocess
- except ImportError:
- try:
- Popen3 = popen2.Popen3
- except AttributeError:
- class Popen3:
- def __init__(self, command):
- (stdin, stdout, stderr) = os.popen3(' ' + command)
- self.stdin = stdin
- self.stdout = stdout
- self.stderr = stderr
- def close_output(self):
- self.stdout.close()
- self.resultcode = self.stderr.close()
- def wait(self):
- return self.resultcode
- if sys.platform == 'win32' and cmd_string[0] == '"':
- cmd_string = '"' + cmd_string + '"'
- p = Popen3(cmd_string)
- else:
- p = Popen3(cmd, 1)
- p.stdin = p.tochild
- p.stdout = p.fromchild
- p.stderr = p.childerr
+ combine = kw.get('combine', self.combine)
+ if combine:
+ stderr_value = subprocess.STDOUT
else:
- p = subprocess.Popen(cmd,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=universal_newlines)
+ stderr_value = subprocess.PIPE
+
+ return Popen(cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=stderr_value,
+ universal_newlines=universal_newlines)
+ def finish(self, popen, **kw):
+ """
+ Finishes and waits for the process being run under control of
+ the specified popen argument, recording the exit status,
+ standard output and error output.
+ """
+ popen.stdin.close()
+ self.status = popen.wait()
+ if not self.status:
+ self.status = 0
+ self._stdout.append(popen.stdout.read())
+ if popen.stderr:
+ stderr = popen.stderr.read()
+ else:
+ stderr = ''
+ self._stderr.append(stderr)
+
+ def run(self, program = None,
+ interpreter = None,
+ arguments = None,
+ chdir = None,
+ stdin = None,
+ universal_newlines = None):
+ """Runs a test of the program or script for the test
+ environment. Standard output and error output are saved for
+ future retrieval via the stdout() and stderr() methods.
+
+ The specified program will have the original directory
+ prepended unless it is enclosed in a [list].
+ """
+ if chdir:
+ oldcwd = os.getcwd()
+ if not os.path.isabs(chdir):
+ chdir = os.path.join(self.workpath(chdir))
+ if self.verbose:
+ sys.stderr.write("chdir(" + chdir + ")\n")
+ os.chdir(chdir)
+ p = self.start(program, interpreter, arguments, universal_newlines)
if stdin:
if is_List(stdin):
for line in stdin:
@@ -788,23 +1043,26 @@ class TestCmd:
p.stdin.close()
out = p.stdout.read()
- err = p.stderr.read()
+ if p.stderr is None:
+ err = ''
+ else:
+ err = p.stderr.read()
try:
- p.close_output()
+ close_output = p.close_output
except AttributeError:
p.stdout.close()
- p.stderr.close()
+ if not p.stderr is None:
+ p.stderr.close()
+ else:
+ close_output()
+
+ self._stdout.append(out)
+ self._stderr.append(err)
self.status = p.wait()
if not self.status:
self.status = 0
- if self.combine:
- self._stdout.append(out + err)
- else:
- self._stdout.append(out)
- self._stderr.append(err)
-
if chdir:
os.chdir(oldcwd)
if self.verbose >= 2:
@@ -990,18 +1248,24 @@ class TestCmd:
def readable(self, top, read=1):
"""Make the specified directory tree readable (read == 1)
or not (read == None).
+
+ This method has no effect on Windows systems, which use a
+ completely different mechanism to control file readability.
"""
+ if sys.platform == 'win32':
+ return
+
if read:
def do_chmod(fname):
try: st = os.stat(fname)
except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0400))
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IREAD))
else:
def do_chmod(fname):
try: st = os.stat(fname)
except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0400))
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IREAD))
if os.path.isfile(top):
# If it's a file, that's easy, just chmod it.
@@ -1040,16 +1304,29 @@ class TestCmd:
or not (write == None).
"""
- if write:
- def do_chmod(fname):
- try: st = os.stat(fname)
- except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
+ if sys.platform == 'win32':
+
+ if write:
+ def do_chmod(fname):
+ try: os.chmod(fname, stat.S_IWRITE)
+ except OSError: pass
+ else:
+ def do_chmod(fname):
+ try: os.chmod(fname, stat.S_IREAD)
+ except OSError: pass
+
else:
- def do_chmod(fname):
- try: st = os.stat(fname)
- except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
+
+ if write:
+ def do_chmod(fname):
+ try: st = os.stat(fname)
+ except OSError: pass
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0200))
+ else:
+ def do_chmod(fname):
+ try: st = os.stat(fname)
+ except OSError: pass
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0200))
if os.path.isfile(top):
do_chmod(top)
@@ -1061,18 +1338,24 @@ class TestCmd:
def executable(self, top, execute=1):
"""Make the specified directory tree executable (execute == 1)
or not (execute == None).
+
+ This method has no effect on Windows systems, which use a
+ completely different mechanism to control file executability.
"""
+ if sys.platform == 'win32':
+ return
+
if execute:
def do_chmod(fname):
try: st = os.stat(fname)
except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|0100))
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]|stat.S_IEXEC))
else:
def do_chmod(fname):
try: st = os.stat(fname)
except OSError: pass
- else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~0100))
+ else: os.chmod(fname, stat.S_IMODE(st[stat.ST_MODE]&~stat.S_IEXEC))
if os.path.isfile(top):
# If it's a file, that's easy, just chmod it.
diff --git a/QMTest/TestCommon.py b/QMTest/TestCommon.py
index d6b21adf..acc63d49 100644
--- a/QMTest/TestCommon.py
+++ b/QMTest/TestCommon.py
@@ -84,9 +84,10 @@ The TestCommon module also provides the following variables
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.30.D001 2007/10/01 16:53:55 knight"
-__version__ = "0.30"
+__revision__ = "TestCommon.py 0.31.D001 2008/01/01 09:05:59 knight"
+__version__ = "0.31"
+import copy
import os
import os.path
import stat
@@ -378,6 +379,97 @@ class TestCommon(TestCmd):
print "Writable files: `%s'" % string.join(writable, "', `")
self.fail_test(missing + writable)
+ def _complete(self, actual_stdout, expected_stdout,
+ actual_stderr, expected_stderr, status, match):
+ """
+ Post-processes running a subcommand, checking for failure
+ status and displaying output appropriately.
+ """
+ if _failed(self, status):
+ expect = ''
+ if status != 0:
+ expect = " (expected %s)" % str(status)
+ print "%s returned %s%s" % (self.program, str(_status(self)), expect)
+ print self.banner('STDOUT ')
+ print actual_stdout
+ print self.banner('STDERR ')
+ print actual_stderr
+ self.fail_test()
+ if not expected_stdout is None and not match(actual_stdout, expected_stdout):
+ self.diff(expected_stdout, actual_stdout, 'STDOUT ')
+ if actual_stderr:
+ print self.banner('STDERR ')
+ print actual_stderr
+ self.fail_test()
+ if not expected_stderr is None and not match(actual_stderr, expected_stderr):
+ print self.banner('STDOUT ')
+ print actual_stdout
+ self.diff(expected_stderr, actual_stderr, 'STDERR ')
+ self.fail_test()
+
+ def start(self, program = None,
+ interpreter = None,
+ arguments = None,
+ universal_newlines = None,
+ **kw):
+ """
+ Starts a program or script for the test environment.
+
+ This handles the "options" keyword argument and exceptions.
+ """
+ try:
+ options = kw['options']
+ del kw['options']
+ except KeyError:
+ pass
+ else:
+ if options:
+ if arguments is None:
+ arguments = options
+ else:
+ arguments = options + " " + arguments
+ try:
+ return apply(TestCmd.start,
+ (self, program, interpreter, arguments, universal_newlines),
+ kw)
+ except KeyboardInterrupt:
+ raise
+ except Exception, e:
+ print self.banner('STDOUT ')
+ try:
+ print self.stdout()
+ except IndexError:
+ pass
+ print self.banner('STDERR ')
+ try:
+ print self.stderr()
+ except IndexError:
+ pass
+ raise e
+
+ def finish(self, popen, stdout = None, stderr = '', status = 0, **kw):
+ """
+ Finishes and waits for the process being run under control of
+ the specified popen argument. Additional arguments are similar
+ to those of the run() method:
+
+ stdout The expected standard output from
+ the command. A value of None means
+ don't test standard output.
+
+ stderr The expected error output from
+ the command. A value of None means
+ don't test error output.
+
+ status The expected exit status from the
+ command. A value of None means don't
+ test exit status.
+ """
+ apply(TestCmd.finish, (self, popen,), kw)
+ match = kw.get('match', self.match)
+ self._complete(self.stdout(), stdout,
+ self.stderr(), stderr, status, match)
+
def run(self, options = None, arguments = None,
stdout = None, stderr = '', status = 0, **kw):
"""Runs the program under test, checking that the test succeeded.
@@ -415,44 +507,9 @@ class TestCommon(TestCmd):
del kw['match']
except KeyError:
match = self.match
- try:
- apply(TestCmd.run, [self], kw)
- except KeyboardInterrupt:
- raise
- except Exception, e:
- print self.banner('STDOUT ')
- try:
- print self.stdout()
- except IndexError:
- pass
- print self.banner('STDERR ')
- try:
- print self.stderr()
- except IndexError:
- pass
- raise e
- if _failed(self, status):
- expect = ''
- if status != 0:
- expect = " (expected %s)" % str(status)
- print "%s returned %s%s" % (self.program, str(_status(self)), expect)
- print self.banner('STDOUT ')
- print self.stdout()
- print self.banner('STDERR ')
- print self.stderr()
- self.fail_test()
- if not stdout is None and not match(self.stdout(), stdout):
- self.diff(stdout, self.stdout(), 'STDOUT ')
- stderr = self.stderr()
- if stderr:
- print self.banner('STDERR ')
- print stderr
- self.fail_test()
- if not stderr is None and not match(self.stderr(), stderr):
- print self.banner('STDOUT ')
- print self.stdout()
- self.diff(stderr, self.stderr(), 'STDERR ')
- self.fail_test()
+ apply(TestCmd.run, [self], kw)
+ self._complete(self.stdout(), stdout,
+ self.stderr(), stderr, status, match)
def skip_test(self, message="Skipping test.\n"):
"""Skips a test.
diff --git a/QMTest/TestSCons.py b/QMTest/TestSCons.py
index b1fdbc1c..6b6f5ed7 100644
--- a/QMTest/TestSCons.py
+++ b/QMTest/TestSCons.py
@@ -21,6 +21,7 @@ import os.path
import re
import string
import sys
+import time
import __builtin__
try:
@@ -138,7 +139,6 @@ def re_escape(str):
return str
-
class TestSCons(TestCommon):
"""Class for testing SCons.
@@ -337,8 +337,8 @@ class TestSCons(TestCommon):
return x
def normalize_pdf(self, s):
- s = re.sub(r'/CreationDate \(D:[^)]*\)',
- r'/CreationDate (D:XXXX)', s)
+ s = re.sub(r'/(Creation|Mod)Date \(D:[^)]*\)',
+ r'/\1Date (D:XXXX)', s)
s = re.sub(r'/ID \[<[0-9a-fA-F]*> <[0-9a-fA-F]*>\]',
r'/ID [<XXXX> <XXXX>]', s)
s = re.sub(r'/(BaseFont|FontName) /[A-Z]{6}',
@@ -381,33 +381,114 @@ class TestSCons(TestCommon):
return s
- def java_ENV(self):
+ def java_ENV(self, version=None):
"""
- Return a default external environment that uses a local Java SDK
- in preference to whatever's found in the default PATH.
+ Initialize with a default external environment that uses a local
+ Java SDK in preference to whatever's found in the default PATH.
"""
+ try:
+ return self._java_env[version]['ENV']
+ except AttributeError:
+ self._java_env = {}
+ except KeyError:
+ pass
+
import SCons.Environment
env = SCons.Environment.Environment()
- java_path = [
- '/usr/local/j2sdk1.4.2/bin',
- '/usr/local/j2sdk1.4.1/bin',
- '/usr/local/j2sdk1.3.1/bin',
- '/usr/local/j2sdk1.3.0/bin',
- '/usr/local/j2sdk1.2.2/bin',
- '/usr/local/j2sdk1.2/bin',
- '/usr/local/j2sdk1.1.8/bin',
- '/usr/local/j2sdk1.1.7/bin',
- '/usr/local/j2sdk1.1.6/bin',
- '/usr/local/j2sdk1.1.5/bin',
- '/usr/local/j2sdk1.1.4/bin',
- '/usr/local/j2sdk1.1.3/bin',
- '/usr/local/j2sdk1.1.2/bin',
- '/usr/local/j2sdk1.1.1/bin',
- env['ENV']['PATH'],
- ]
+ self._java_env[version] = env
+
+ def paths(patterns):
+ import glob
+ result = []
+ for p in patterns:
+ paths = glob.glob(p)
+ paths.sort()
+ result.extend(paths)
+ return result
+
+ if version:
+ patterns = [
+ '/usr/lib/jvm/*-%s*/bin' % version,
+ '/usr/local/j2sdk%s*/bin' % version,
+ ]
+ java_path = paths(patterns) + [env['ENV']['PATH']]
+ else:
+ patterns = [
+ '/usr/lib/jvm/*/bin',
+ '/usr/local/j2sdk*/bin',
+ ]
+ java_path = paths(patterns) + [env['ENV']['PATH']]
+
env['ENV']['PATH'] = string.join(java_path, os.pathsep)
return env['ENV']
+ def java_where_jar(self, version=None):
+ ENV = self.java_ENV(version)
+ if self.detect_tool('jar', ENV=ENV):
+ where_jar = self.detect('JAR', 'jar', ENV=ENV)
+ else:
+ where_jar = self.where_is('jar', ENV['PATH'])
+ if not where_jar:
+ self.skip_test("Could not find Java jar, skipping test(s).\n")
+ return where_jar
+
+ def java_where_java(self, version=None):
+ """
+ Return a path to the java executable.
+ """
+ ENV = self.java_ENV(version)
+ where_java = self.where_is('java', ENV['PATH'])
+ if not where_java:
+ self.skip_test("Could not find Java java, skipping test(s).\n")
+ return where_java
+
+ def java_where_javac(self, version=None):
+ """
+ Return a path to the javac compiler.
+ """
+ ENV = self.java_ENV(version)
+ if self.detect_tool('javac'):
+ where_javac = self.detect('JAVAC', 'javac', ENV=ENV)
+ else:
+ where_javac = self.where_is('javac', ENV['PATH'])
+ if not where_javac:
+ self.skip_test("Could not find Java javac, skipping test(s).\n")
+ self.run(program = where_javac,
+ arguments = '-version',
+ stderr=None,
+ status=None)
+ if version:
+ if string.find(self.stderr(), 'javac %s' % version) == -1:
+ fmt = "Could not find javac for Java version %s, skipping test(s).\n"
+ self.skip_test(fmt % version)
+ else:
+ m = re.search(r'javac (\d\.\d)', self.stderr())
+ if m:
+ version = m.group(1)
+ else:
+ version = None
+ return where_javac, version
+
+ def java_where_javah(self, version=None):
+ ENV = self.java_ENV(version)
+ if self.detect_tool('javah'):
+ where_javah = self.detect('JAVAH', 'javah', ENV=ENV)
+ else:
+ where_javah = self.where_is('javah', ENV['PATH'])
+ if not where_javah:
+ self.skip_test("Could not find Java javah, skipping test(s).\n")
+ return where_javah
+
+ def java_where_rmic(self, version=None):
+ ENV = self.java_ENV(version)
+ if self.detect_tool('rmic'):
+ where_rmic = self.detect('RMIC', 'rmic', ENV=ENV)
+ else:
+ where_rmic = self.where_is('rmic', ENV['PATH'])
+ if not where_rmic:
+ self.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
+ return where_rmic
+
def Qt_dummy_installation(self, dir='qt'):
# create a dummy qt installation
@@ -840,6 +921,22 @@ print "self._msvs_versions =", str(env['MSVS']['VERSIONS'])
else:
return distutils.sysconfig.get_python_inc()
+ def wait_for(self, fname, timeout=10.0, popen=None):
+ """
+ Waits for the specified file name to exist.
+ """
+ waited = 0.0
+ while not os.path.exists(fname):
+ if timeout and waited >= timeout:
+ sys.stderr.write('timed out waiting for %s to exist\n' % fname)
+ if popen:
+ popen.stdin.close()
+ self.status = 1
+ self.finish(popen)
+ self.fail_test()
+ time.sleep(1.0)
+ waited = waited + 1.0
+
# In some environments, $AR will generate a warning message to stderr
# if the library doesn't previously exist and is being created. One
# way to fix this is to tell AR to be quiet (sometimes the 'c' flag),
diff --git a/QMTest/TestSCons_time.py b/QMTest/TestSCons_time.py
index 102181e4..f3ea49a9 100644
--- a/QMTest/TestSCons_time.py
+++ b/QMTest/TestSCons_time.py
@@ -246,6 +246,30 @@ class TestSCons_time(TestCommon):
self.write(python_name, profile_py % d)
self.run(program = python_name, interpreter = sys.executable)
+ def tempdir_re(self, *args):
+ """
+ Returns a regular expression to match a scons-time
+ temporary directory.
+ """
+ import re
+ import tempfile
+
+ sep = re.escape(os.sep)
+ tempdir = tempfile.gettempdir()
+
+ try:
+ realpath = os.path.realpath
+ except AttributeError:
+ pass
+ else:
+ tempdir = realpath(tempdir)
+
+ args = (tempdir, 'scons-time-',) + args
+ x = apply(os.path.join, args)
+ x = re.escape(x)
+ x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
+ return x
+
def write_fake_aegis_py(self, name):
name = self.workpath(name)
self.write(name, aegis_py)
diff --git a/doc/man/scons-time.1 b/doc/man/scons-time.1
index b2de0029..07832a99 100644
--- a/doc/man/scons-time.1
+++ b/doc/man/scons-time.1
@@ -855,6 +855,19 @@ with the
.B --aegis=
command-line option.
.TP
+.B archive_list
+A list of archives (files or directories)
+that will be copied to the temporary directory
+in which SCons will be invoked.
+.BR .tar ,
+.BR .tar.gz ,
+.BR .tgz
+and
+.BR .zip
+files will have their contents unpacked in
+the temporary directory.
+Directory trees and files will be copied as-is.
+.TP
.B initial_commands
A list of commands that will be executed
before the actual timed
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index 526a5a3c..ae252747 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -868,6 +868,132 @@ This implies
.BR --implicit-cache .
.TP
+--interactive
+Starts SCons in interactive mode.
+The SConscript files are read once and a
+.B "scons>>>"
+prompt is printed.
+Targets may now be rebuilt by typing commands at interactive prompt
+without having to re-read the SConscript files
+and re-initialize the dependency graph from scratch.
+
+SCons interactive mode supports the following commands:
+
+.RS 10
+.TP 6
+.BI build "[OPTIONS] [TARGETS] ..."
+Builds the specified
+.I TARGETS
+(and their dependencies)
+with the specified
+SCons command-line
+.IR OPTIONS .
+.B b
+and
+.B scons
+are synonyms.
+
+The following SCons command-line options affect the
+.B build
+command:
+
+.ES
+--cache-debug=FILE
+--cache-disable, --no-cache
+--cache-force, --cache-populate
+--cache-show
+--debug=TYPE
+-i, --ignore-errors
+-j N, --jobs=N
+-k, --keep-going
+-n, --no-exec, --just-print, --dry-run, --recon
+-Q
+-s, --silent, --quiet
+-s, --silent, --quiet
+--taskmastertrace=FILE
+--tree=OPTIONS
+.EE
+
+.IP "" 6
+Any other SCons command-line options that are specified
+do not cause errors
+but have no effect on the
+.B build
+command
+(mainly because they affect how the SConscript files are read,
+which only happens once at the beginning of interactive mode).
+
+.TP 6
+.BI clean "[OPTIONS] [TARGETS] ..."
+Cleans the specified
+.I TARGETS
+(and their dependencies)
+with the specified options.
+.B c
+is a synonym.
+This command is itself a synonym for
+.B "build --clean"
+
+.TP 6
+.BI exit
+Exits SCons interactive mode.
+You can also exit by terminating input
+(CTRL+D on UNIX or Linux systems,
+CTRL+Z on Windows systems).
+
+.TP 6
+.BI help "[COMMAND]"
+Provides a help message about
+the commands available in SCons interactive mode.
+If
+.I COMMAND
+is specified,
+.B h
+and
+.B ?
+are synonyms.
+
+.TP 6
+.BI shell "[COMMANDLINE]"
+Executes the specified
+.I COMMANDLINE
+in a subshell.
+If no
+.I COMMANDLINE
+is specified,
+executes the interactive command interpreter
+specified in the
+.B SHELL
+environment variable
+(on UNIX and Linux systems)
+or the
+.B COMSPEC
+environment variable
+(on Windows systems).
+.B sh
+and
+.B !
+are synonyms.
+
+.TP 6
+.B version
+Prints SCons version information.
+.RE
+
+An empty line repeats the last typed command.
+Command-line editing can be used if the
+.B readline
+module is available.
+
+.ES
+$ scons --interactive
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons>>> build -n prog
+scons>>> exit
+.EE
+
+.TP
.RI -j " N" ", --jobs=" N
Specifies the number of jobs (commands) to run simultaneously.
If there is more than one
@@ -1044,6 +1170,26 @@ will get loaded if it exists, and
will get added to the default toolpath.
.TP
+.RI --stack-size= KILOBYTES
+Set the size stack used to run threads to
+.IR KILOBYTES .
+This value determines the stack size of the threads used to run jobs.
+These are the threads that execute the actions of the builders for the
+nodes that are out-of-date.
+Note that this option has no effect unless the
+.B num_jobs
+option, which corresponds to -j and --jobs, is larger than one. Using
+a stack size that is too small may cause stack overflow errors. This
+usually shows up as segmentation faults that cause scons to abort
+before building anything. Using a stack size that is too large will
+cause scons to use more memory than required and may slow down the entire
+build process.
+
+The default value is to use a stack size of 256 kilobytes, which should
+be appropriate for most uses. You should not need to increase this value
+unless you encounter stack overflow errors.
+
+.TP
-t, --touch
Ignored for compatibility with GNU
.BR make .
@@ -1244,6 +1390,12 @@ or
.BR SOURCES .
These warnings are disabled by default.
+.TP
+--warn=stack-size, --warn=no-stack-size
+Enables or disables warnings about requests to set the stack size
+that could not be honored.
+These warnings are enabled by default.
+
.\" .TP
.\" .RI --write-filenames= file
.\" Write all filenames considered into
@@ -1462,6 +1614,7 @@ g++
g77
gas
gcc
+gfortran
gnulink
gs
hpc++
@@ -2917,6 +3070,7 @@ that specify the type of decision function
to be performed:
.RS 10
+.HP 6
.B timestamp-newer
Specifies that a target shall be considered out of date and rebuilt
if the dependency's timestamp is newer than the target file's timestamp.
@@ -4949,9 +5103,11 @@ which corresponds to --max-drift;
.B no_exec
which corresponds to -n, --no-exec, --just-print, --dry-run and --recon;
.B num_jobs
-which corresponds to -j and --jobs.
+which corresponds to -j and --jobs;
.B random
-which corresponds to --random.
+which corresponds to --random; and
+.B stack_size
+which corresponds to --stack-size.
See the documentation for the
corresponding command line object for information about each specific
option.
@@ -5066,9 +5222,10 @@ env.SourceCode('no_source.c', None)
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
-.RI env.subst( string ", [" raw ", " target ", " source ", " conv ])
+.RI env.subst( input ", [" raw ", " target ", " source ", " conv ])
Performs construction variable interpolation
-on the specified string argument.
+on the specified string or sequence argument
+.IR input .
By default,
leading or trailing white space will
@@ -5100,6 +5257,12 @@ and
pairs
(as is done for signature calculation).
+If the input is a sequence
+(list or tuple),
+the individual elements of
+the sequence will be expanded,
+and the results will be returned as a list.
+
The optional
.I target
and
@@ -5120,9 +5283,8 @@ calling
from within a Python function used
as an SCons action.
-By default,
-all returned values are converted
-to their string representation.
+Returned string values or sequence elements
+are converted to their string representation by default.
The optional
.I conv
argument
@@ -6251,6 +6413,82 @@ will return success only if short is two bytes.
.ES
.EE
+.TP
+.RI Configure.CheckDeclaration( self ", " symbol ", [" includes ", " language ])
+Checks if the specified
+.I symbol
+is declared.
+.I includes
+is a string containing one or more
+.B #include
+lines that will be inserted into the program
+that will be run to test for the existence of the type.
+The optional
+.I language
+argument should be
+.B C
+or
+.B C++
+and selects the compiler to be used for the check;
+the default is "C".
+
+.TP
+.RI Configure.Define(self ", " symbol ", [" value ", " comment ])
+This function does not check for anything, but defines a
+preprocessor symbol that will be added to the configuration header file.
+It is the equivalent of AC_DEFINE,
+and defines the symbol
+.I name
+with the optional
+.B value
+and the optional comment
+.BR comment .
+
+.IP
+Examples:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following line in the config header file:
+# #define A_SYMBOL
+conf.Define('A_SYMBOL')
+
+# Puts the following line in the config header file:
+# #define A_SYMBOL 1
+conf.Define('A_SYMBOL', 1)
+.EE
+
+.IP
+Be careful about quoting string values, though:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following line in the config header file:
+# #define A_SYMBOL YA
+conf.Define('A_SYMBOL', "YA")
+
+# Puts the following line in the config header file:
+# #define A_SYMBOL "YA"
+conf.Define('A_SYMBOL', '"YA"')
+.EE
+
+.IP
+For comment:
+
+.ES
+env = Environment()
+conf = Configure( env )
+
+# Puts the following lines in the config header file:
+# /* Set to 1 if you have a symbol */
+# #define A_SYMBOL 1
+conf.Define('A_SYMBOL', 1, 'Set to 1 if you have a symbol')
+.EE
+
.EE
You can define your own custom checks.
in addition to the predefined checks.
diff --git a/doc/scons.mod b/doc/scons.mod
index c23e6ae0..e590368d 100644
--- a/doc/scons.mod
+++ b/doc/scons.mod
@@ -22,6 +22,7 @@
<!ENTITY as "<application>as</application>">
<!ENTITY Autoconf "<application>Autoconf</application>">
<!ENTITY Automake "<application>Automake</application>">
+<!ENTITY bison "<application>bison</application>">
<!ENTITY cc "<application>cc</application>">
<!ENTITY Cons "<application>Cons</application>">
<!ENTITY cp "<application>cp</application>">
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index c8dc7d3f..81b54e65 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -8,6 +8,141 @@
+RELEASE 0.XX - XXX
+
+ From Benoit Belley:
+
+ - Fix the --keep-going flag so it builds all possible targets even when
+ a later top-level target depends on a child that failed its build.
+
+ - Fix being able to use $PDB and $WINDWOWS_INSERT_MANIFEST together.
+
+ - Don't crash if un-installing the Intel C compiler leaves left-over,
+ dangling entries in the Windows registry.
+
+ - Improve support for non-standard library prefixes and suffixes by
+ stripping all prefixes/suffixes from file name string as appropriate.
+
+ - Reduce the default stack size for -j worker threads to 256 Kbytes.
+ Provide user control over this value by adding --stack-size and
+ --warn=stack-size options, and a SetOption('stack_size') function.
+
+ - Fix a crash on Linux systems when trying to use the Intel C compiler
+ and no /opt/intel_cc_* directories are found.
+
+ - Improve using Python functions as actions by incorporating into
+ a FunctionAction's signature:
+ - literal values referenced by the byte code.
+ - values of default arguments
+ - code of nested functions
+ - values of variables captured by closures
+ - names of referenced global variables and functions
+
+ - Fix the closing message when --clean and --keep-going are both
+ used and no errors occur.
+
+ - Add support for the Intel C compiler on Mac OS X.
+
+ From Jérôme Berger:
+
+ - Have the D language scanner search for .di files as well as .d files.
+
+ - Add a find_include_names() method to the Scanner.Classic class to
+ abstract out how included names can be generated by subclasses.
+
+ - Allow the D language scanner to detect multiple modules imported by
+ a single statement.
+
+ From Konstantin Bozhikov:
+
+ - Support expansion of construction variables that contain or refer
+ to lists of other variables or Nodes within expansions like $PCPPATH.
+
+ - Change variable substitution (the env.subst() method) so that an
+ input sequence (list or tuple) is preserved as a list in the output.
+
+ From David Cournapeau:
+
+ - Add a CheckDeclaration() call to configure contexts.
+
+ - Improve the CheckTypeSize() code.
+
+ - Add a Define() call to configure contexts, to add arbitrary #define
+ lines to a generated configure header file.
+
+ - Add a "gfortran" Tool module for the GNU F95/F2003 compiler.
+
+ - Avoid use of -rpath with the Mac OS X linker.
+
+ From Steven Knight:
+
+ - Support the ability to subclass the new-style "str" class as input
+ to Builders.
+
+ - Improve the performance of our type-checking by using isinstance()
+ with new-style classes.
+
+ - Fix #include (and other $*PATH variables searches) of files with
+ absolute path names. Don't die if they don't exist (due to being
+ #ifdef'ed out or the like).
+
+ - Fix --interactive mode when Default(None) is used.
+
+ - Fix --debug=memoizer to work around a bug in base Python 2.2 metaclass
+ initialization (by just not allowing Memoization in Python versions
+ that have the bug).
+
+ - Have the "scons-time time" subcommand handle empty log files, and
+ log files that contain no results specified by the --which option.
+
+ - Fix the max Y of vertical bars drawn by "scons-time --fmt=gnuplot".
+
+ - On Mac OS X, account for the fact that the header file generated
+ from a C++ file will be named (e.g.) file.cpp.h, not file.hpp.
+
+ From Rob Managan:
+
+ - Enhance TeX and LaTeX support to work with BuildDir(duplicate=0).
+
+ - Re-run LaTeX when it issues a package warning that it must be re-run.
+
+ From Jan Nijtmans:
+
+ - If $JARCHDIR isn't set explicitly, use the .java_classdir attribute
+ that was set when the Java() Builder built the .class files.
+
+ From Gary Oberbrunner:
+
+ - Fix the ability to build an Alias in --interactive mode.
+
+ - Fix the ability to hash the contents of actions for nested Python
+ functions on Python versions where the inability to pickle them
+ returns a TypeError (instead of the documented PicklingError).
+
+ From Jonas Olsson:
+
+ - Fix use of the Intel C compiler when the top compiler directory,
+ but not the compiler version, is specified.
+
+ - Handle Intel C compiler network license files (port@system).
+
+ From Adam Simpkins:
+
+ - Add a --interactive option that starts a session for building (or
+ cleaning) targets without re-reading the SConscript files every time.
+
+ - Fix use of readline command-line editing in --interactive mode.
+
+ - Have the --interactive mode "build" command with no arguments
+ build the specified Default() targets.
+
+ From Ben Webb:
+
+ - Support the SWIG %module statement with following modifiers in
+ parenthese (e.g., '%module(directors="1")').
+
+
+
RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
From Benoit Belley:
diff --git a/src/RELEASE.txt b/src/RELEASE.txt
index ca01607b..7327f21b 100644
--- a/src/RELEASE.txt
+++ b/src/RELEASE.txt
@@ -25,6 +25,43 @@ RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
This is the eighth beta release of SCons. Please consult the
CHANGES.txt file for a list of specific changes since last release.
+ Please note the following important changes since release 0.97.0d20071212:
+
+ -- THE env.subst() METHOD NOW RETURNS A LIST WHEN THE INPUT IS A SEQUENCE
+
+ The env.subst() method now returns a list with the elements
+ expanded when given a list as input. Previously, the env.subst()
+ method would always turn its result into a string.
+
+ This behavior was changed because way it interfered with
+ being able to include things like lists within the expansion
+ of variables like $CPPPATH and have SCons understand that the
+ elements of the "internal" lists still needed to be treated
+ separately. This would show up as a list like ['subdir1',
+ 'subdir'] showing up in a command line as "-Isubdir1 subdir".
+
+ -- THE Jar() BUILDER NOW USES THE Java() BUILDER CLASSDIR BY DEFAULT
+
+ By default, the Jar() Builder will now use the class directory
+ specified when the Java() builder is called. So the following
+ input:
+
+ classes = env.Java('classes', 'src')
+ env.Jar('out.jar', classes)
+
+ Will cause "-C classes" to be passed the "jar" command invocation,
+ and the Java classes in the "out.jar" file will not be prefixed
+ "classes/".
+
+ Explicitly setting the $JARCHDIR variable overrides this default
+ behavior. The old behavior of not passing any -C option to the
+ "jar" command can be preserved by explicitly setting $JARCHDIR
+ to None:
+
+ env = Environment(JARCHDIR = None)
+
+ The above setting is compatible with older versions of SCons.
+
Please note the following important changes since release 0.97.0d20070918:
-- SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS
diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in
index 52323d20..093fbd95 100644
--- a/src/engine/MANIFEST.in
+++ b/src/engine/MANIFEST.in
@@ -6,6 +6,7 @@ SCons/compat/_scons_hashlib.py
SCons/compat/_scons_optparse.py
SCons/compat/_scons_sets.py
SCons/compat/_scons_sets15.py
+SCons/compat/_scons_shlex.py
SCons/compat/_scons_subprocess.py
SCons/compat/_scons_textwrap.py
SCons/compat/_scons_UserString.py
@@ -54,6 +55,7 @@ SCons/Scanner/Prog.py
SCons/SConf.py
SCons/SConsign.py
SCons/Script/__init__.py
+SCons/Script/Interactive.py
SCons/Script/Main.py
SCons/Script/SConscript.py
SCons/Script/SConsOptions.py
@@ -89,6 +91,7 @@ SCons/Tool/g++.py
SCons/Tool/g77.py
SCons/Tool/gas.py
SCons/Tool/gcc.py
+SCons/Tool/gfortran.py
SCons/Tool/gnulink.py
SCons/Tool/gs.py
SCons/Tool/hpc++.py
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index c2c11581..cd4bf6aa 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -97,6 +97,7 @@ way for wrapping up the functions.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import cPickle
import dis
import os
import os.path
@@ -150,6 +151,138 @@ else:
i = i+1
return string.join(result, '')
+
+def _callable_contents(obj):
+ """Return the signature contents of a callable Python object.
+ """
+ try:
+ # Test if obj is a method.
+ return _function_contents(obj.im_func)
+
+ except AttributeError:
+ try:
+ # Test if obj is a callable object.
+ return _function_contents(obj.__call__.im_func)
+
+ except AttributeError:
+ try:
+ # Test if obj is a code object.
+ return _code_contents(obj)
+
+ except AttributeError:
+ # Test if obj is a function object.
+ return _function_contents(obj)
+
+
+def _object_contents(obj):
+ """Return the signature contents of any Python object.
+
+ We have to handle the case where object contains a code object
+ since it can be pickled directly.
+ """
+ try:
+ # Test if obj is a method.
+ return _function_contents(obj.im_func)
+
+ except AttributeError:
+ try:
+ # Test if obj is a callable object.
+ return _function_contents(obj.__call__.im_func)
+
+ except AttributeError:
+ try:
+ # Test if obj is a code object.
+ return _code_contents(obj)
+
+ except AttributeError:
+ try:
+ # Test if obj is a function object.
+ return _function_contents(obj)
+
+ except AttributeError:
+ # Should be a pickable Python object.
+ try:
+ return cPickle.dumps(obj)
+ except (cPickle.PicklingError, TypeError):
+ # This is weird, but it seems that nested classes
+ # are unpickable. The Python docs say it should
+ # always be a PicklingError, but some Python
+ # versions seem to return TypeError. Just do
+ # the best we can.
+ return str(obj)
+
+
+def _code_contents(code):
+ """Return the signature contents of a code object.
+
+ By providing direct access to the code object of the
+ function, Python makes this extremely easy. Hooray!
+
+ Unfortunately, older versions of Python include line
+ number indications in the compiled byte code. Boo!
+ So we remove the line number byte codes to prevent
+ recompilations from moving a Python function.
+ """
+
+ contents = []
+
+ # The code contents depends on the number of local variables
+ # but not their actual names.
+ contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames)))
+ try:
+ contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars)))
+ except AttributeError:
+ # Older versions of Python do not support closures.
+ contents.append(",0,0")
+
+ # The code contents depends on any constants accessed by the
+ # function. Note that we have to call _object_contents on each
+ # constants because the code object of nested functions can
+ # show-up among the constants.
+ #
+ # Note that we also always ignore the first entry of co_consts
+ # which contains the function doc string. We assume that the
+ # function does not access its doc string.
+ contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')')
+
+ # The code contents depends on the variable names used to
+ # accessed global variable, as changing the variable name changes
+ # the variable actually accessed and therefore changes the
+ # function result.
+ contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')')
+
+
+ # The code contents depends on its actual code!!!
+ contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')')
+
+ return string.join(contents, '')
+
+
+def _function_contents(func):
+ """Return the signature contents of a function."""
+
+ contents = [_code_contents(func.func_code)]
+
+ # The function contents depends on the value of defaults arguments
+ if func.func_defaults:
+ contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')')
+ else:
+ contents.append(',()')
+
+ # The function contents depends on the closure captured cell values.
+ try:
+ closure = func.func_closure or []
+ except AttributeError:
+ # Older versions of Python do not support closures.
+ closure = []
+
+ #xxx = [_object_contents(x.cell_contents) for x in closure]
+ xxx = map(lambda x: _object_contents(x.cell_contents), closure)
+ contents.append(',(' + string.join(xxx, ',') + ')')
+
+ return string.join(contents, '')
+
+
def _actionAppend(act1, act2):
# This function knows how to slap two actions together.
# Mainly, it handles ListActions by concatenating into
@@ -643,6 +776,16 @@ class FunctionAction(_ActionAction):
'accepts (target, source, env) as parameters.')
self.execfunction = execfunction
+ try:
+ self.funccontents = _callable_contents(execfunction)
+ except AttributeError:
+ try:
+ # See if execfunction will do the heavy lifting for us.
+ self.gc = execfunction.get_contents
+ except AttributeError:
+ # This is weird, just do the best we can.
+ self.funccontents = _object_contents(execfunction)
+
apply(_ActionAction.__init__, (self,)+args, kw)
self.varlist = kw.get('varlist', [])
self.cmdstr = cmdstr
@@ -716,46 +859,14 @@ class FunctionAction(_ActionAction):
return result
def get_contents(self, target, source, env):
- """Return the signature contents of this callable action.
-
- By providing direct access to the code object of the
- function, Python makes this extremely easy. Hooray!
-
- Unfortunately, older versions of Python include line
- number indications in the compiled byte code. Boo!
- So we remove the line number byte codes to prevent
- recompilations from moving a Python function.
- """
- execfunction = self.execfunction
+ """Return the signature contents of this callable action."""
try:
- # Test if execfunction is a function.
- code = execfunction.func_code.co_code
+ contents = self.gc(target, source, env)
except AttributeError:
- try:
- # Test if execfunction is a method.
- code = execfunction.im_func.func_code.co_code
- except AttributeError:
- try:
- # Test if execfunction is a callable object.
- code = execfunction.__call__.im_func.func_code.co_code
- except AttributeError:
- try:
- # See if execfunction will do the heavy lifting for us.
- gc = self.execfunction.get_contents
- except AttributeError:
- # This is weird, just do the best we can.
- contents = str(self.execfunction)
- else:
- contents = gc(target, source, env)
- else:
- contents = str(code)
- else:
- contents = str(code)
- else:
- contents = str(code)
- contents = remove_set_lineno_codes(contents)
+ contents = self.funccontents
+
return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
- self.varlist)))
+ self.varlist)))
def get_implicit_deps(self, target, source, env):
return []
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 06030e3b..2ad4bef7 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -1490,25 +1490,30 @@ class FunctionActionTestCase(unittest.TestCase):
def LocalFunc():
pass
- matches = [
- "d\000\000S",
- "d\x00\x00S",
+ func_matches = [
+ "0,0,0,0,(),(),(d\000\000S),(),()",
+ "0,0,0,0,(),(),(d\x00\x00S),(),()",
+ ]
+
+ meth_matches = [
+ "1,1,0,0,(),(),(d\000\000S),(),()",
+ "1,1,0,0,(),(),(d\x00\x00S),(),()",
]
a = SCons.Action.FunctionAction(GlobalFunc)
c = a.get_contents(target=[], source=[], env=Environment())
- assert c in matches, repr(c)
+ assert c in func_matches, repr(c)
a = SCons.Action.FunctionAction(LocalFunc)
c = a.get_contents(target=[], source=[], env=Environment())
- assert c in matches, repr(c)
+ assert c in func_matches, repr(c)
a = SCons.Action.FunctionAction(GlobalFunc, varlist=['XYZ'])
- matches_foo = map(lambda x: x + "foo", matches)
+ matches_foo = map(lambda x: x + "foo", func_matches)
c = a.get_contents(target=[], source=[], env=Environment())
- assert c in matches, repr(c)
+ assert c in func_matches, repr(c)
c = a.get_contents(target=[], source=[], env=Environment(XYZ = 'foo'))
assert c in matches_foo, repr(c)
@@ -1525,7 +1530,7 @@ class FunctionActionTestCase(unittest.TestCase):
lc = LocalClass()
a = SCons.Action.FunctionAction(lc.LocalMethod)
c = a.get_contents(target=[], source=[], env=Environment())
- assert c in matches, repr(c)
+ assert c in meth_matches, repr(c)
def test_strfunction(self):
"""Test the FunctionAction.strfunction() method
diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py
index 9b2b4b4b..7caee618 100644
--- a/src/engine/SCons/CacheDir.py
+++ b/src/engine/SCons/CacheDir.py
@@ -34,6 +34,7 @@ import sys
import SCons.Action
+cache_enabled = True
cache_debug = False
cache_force = False
cache_show = False
@@ -129,31 +130,33 @@ class CacheDir:
except ImportError:
msg = "No hashlib or MD5 module available, CacheDir() not supported"
SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
+ self.path = None
else:
self.path = path
+ self.current_cache_debug = None
+ self.debugFP = None
- def CacheDebugWrite(self, fmt, target, cachefile):
- self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
-
- def CacheDebugQuiet(self, fmt, target, cachefile):
- pass
-
- def CacheDebugInit(self, fmt, target, cachefile):
- if cache_debug:
+ def CacheDebug(self, fmt, target, cachefile):
+ if cache_debug != self.current_cache_debug:
if cache_debug == '-':
self.debugFP = sys.stdout
- else:
+ elif cache_debug:
self.debugFP = open(cache_debug, 'w')
- self.CacheDebug = self.CacheDebugWrite
- self.CacheDebug(fmt, target, cachefile)
- else:
- self.CacheDebug = self.CacheDebugQuiet
+ else:
+ self.debugFP = None
+ self.current_cache_debug = cache_debug
+ if self.debugFP:
+ self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
- CacheDebug = CacheDebugInit
+ def is_enabled(self):
+ return (cache_enabled and not self.path is None)
def cachepath(self, node):
"""
"""
+ if not self.is_enabled():
+ return None, None
+
sig = node.get_cachedir_bsig()
subdir = string.upper(sig[0])
dir = os.path.join(self.path, subdir)
@@ -184,6 +187,9 @@ class CacheDir:
execute the CacheRetrieveFunc and then have the latter
explicitly check SCons.Action.execute_actions itself.
"""
+ if not self.is_enabled():
+ return False
+
retrieved = False
if cache_show:
@@ -202,16 +208,10 @@ class CacheDir:
return retrieved
def push(self, node):
+ if not self.is_enabled():
+ return
return CachePush(node, [], node.get_build_env())
def push_if_forced(self, node):
if cache_force:
return self.push(node)
-
-class Null(SCons.Util.Null):
- def repr(self):
- return 'CacheDir.Null()'
- def cachepath(self, node):
- return None, None
- def retrieve(self, node):
- return False
diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py
index fcf8c5a7..33899f60 100644
--- a/src/engine/SCons/Conftest.py
+++ b/src/engine/SCons/Conftest.py
@@ -371,11 +371,10 @@ int main()
}
"""
- # XXX: Try* vs CompileProg ?
- st = context.TryCompile(src % (type_name, expect), suffix)
- if st:
- _Have(context, "SIZEOF_" + type_name, str(expect))
+ st = context.CompileProg(src % (type_name, expect), suffix)
+ if not st:
context.Display("yes\n")
+ _Have(context, "SIZEOF_%s" % type_name, expect)
return expect
else:
context.Display("no\n")
@@ -400,21 +399,76 @@ int main() {
return 0;
}
"""
- ret = context.TryRun(src, suffix)
- st = ret[0]
+ st, out = context.RunProg(src, suffix)
try:
- size = int(ret[1])
- _Have(context, "SIZEOF_" + type_name, str(size))
- context.Display("%d\n" % size)
+ size = int(out)
except ValueError:
+ # If cannot convert output of test prog to an integer (the size),
+ # something went wront, so just fail
+ st = 1
size = 0
- _LogFailed(context, src, st)
- context.Display(" Failed !\n")
- if st:
+
+ if not st:
+ context.Display("yes\n")
+ _Have(context, "SIZEOF_%s" % type_name, size)
return size
else:
+ context.Display("no\n")
+ _LogFailed(context, src, st)
return 0
+ return 0
+
+def CheckDeclaration(context, symbol, includes = None, language = None):
+ """Checks whether symbol is declared.
+
+ Use the same test as autoconf, that is test whether the symbol is defined
+ as a macro or can be used as an r-value.
+
+ Arguments:
+ symbol : str
+ the symbol to check
+ includes : str
+ Optional "header" can be defined to include a header file.
+ language : str
+ only C and C++ supported.
+
+ Returns:
+ status : bool
+ True if the check failed, False if succeeded."""
+
+ # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
+ if context.headerfilename:
+ includetext = '#include "%s"' % context.headerfilename
+ else:
+ includetext = ''
+
+ if not includes:
+ includes = ""
+
+ lang, suffix, msg = _lang2suffix(language)
+ if msg:
+ context.Display("Cannot check for declaration %s: %s\n" % (type_name, msg))
+ return msg
+
+ src = includetext + includes
+ context.Display('Checking whether %s is declared... ' % symbol)
+
+ src = src + r"""
+int main()
+{
+#ifndef %s
+ (void) %s;
+#endif
+ ;
+ return 0;
+}
+""" % (symbol, symbol)
+
+ st = context.CompileProg(src, suffix)
+ _YesNoResult(context, st, "HAVE_DECL_" + symbol, src)
+ return st
+
def CheckLib(context, libs, func_name = None, header = None,
extra_libs = None, call = None, language = None, autoadd = 1):
"""
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index c3d30cb2..3cd47efe 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -93,7 +93,7 @@ def DefaultEnvironment(*args, **kw):
_default_env.Decider('timestamp-match')
global DefaultEnvironment
DefaultEnvironment = _fetch_DefaultEnvironment
- _default_env._CacheDir = SCons.CacheDir.Null()
+ _default_env._CacheDir_path = None
return _default_env
# Emitters for setting the shared attribute on object files,
@@ -270,7 +270,7 @@ def _concat_ixes(prefix, list, suffix, env):
return result
-def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
+def _stripixes(prefix, list, suffix, stripprefixes, stripsuffixes, env, c=None):
"""
This is a wrapper around _concat()/_concat_ixes() that checks for the
existence of prefixes or suffixes on list elements and strips them
@@ -295,19 +295,39 @@ def _stripixes(prefix, list, suffix, stripprefix, stripsuffix, env, c=None):
if SCons.Util.is_List(list):
list = SCons.Util.flatten(list)
- lsp = len(stripprefix)
- lss = len(stripsuffix)
+ if SCons.Util.is_List(stripprefixes):
+ stripprefixes = map(env.subst, SCons.Util.flatten(stripprefixes))
+ else:
+ stripprefixes = [env.subst(stripprefixes)]
+
+ if SCons.Util.is_List(stripsuffixes):
+ stripsuffixes = map(env.subst, SCons.Util.flatten(stripsuffixes))
+ else:
+ stripsuffixes = [stripsuffixes]
+
stripped = []
for l in SCons.PathList.PathList(list).subst_path(env, None, None):
if isinstance(l, SCons.Node.FS.File):
stripped.append(l)
continue
+
if not SCons.Util.is_String(l):
l = str(l)
- if l[:lsp] == stripprefix:
- l = l[lsp:]
- if l[-lss:] == stripsuffix:
- l = l[:-lss]
+
+ for stripprefix in stripprefixes:
+ lsp = len(stripprefix)
+ if l[:lsp] == stripprefix:
+ l = l[lsp:]
+ # Do not strip more than one prefix
+ break
+
+ for stripsuffix in stripsuffixes:
+ lss = len(stripsuffix)
+ if l[-lss:] == stripsuffix:
+ l = l[:-lss]
+ # Do not strip more than one suffix
+ break
+
stripped.append(l)
return c(prefix, stripped, suffix, env)
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index cf2d0eb3..02ad3324 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -481,7 +481,7 @@ class SubstitutionEnvironment:
# We have an object plus a string, or multiple
# objects that we need to smush together. No choice
# but to make them into a string.
- p = string.join(map(SCons.Util.to_String, p), '')
+ p = string.join(map(SCons.Util.to_String_for_subst, p), '')
else:
p = s(p)
r.append(p)
@@ -909,11 +909,18 @@ class Base(SubstitutionEnvironment):
def get_CacheDir(self):
try:
- return self._CacheDir
+ path = self._CacheDir_path
except AttributeError:
- cd = SCons.Defaults.DefaultEnvironment()._CacheDir
- self._CacheDir = cd
- return cd
+ path = SCons.Defaults.DefaultEnvironment()._CacheDir_path
+ try:
+ if path == self._last_CacheDir_path:
+ return self._last_CacheDir
+ except AttributeError:
+ pass
+ cd = SCons.CacheDir.CacheDir(path)
+ self._last_CacheDir_path = path
+ self._last_CacheDir = cd
+ return cd
def get_factory(self, factory, default='File'):
"""Return a factory function for creating Nodes for this
@@ -1645,10 +1652,9 @@ class Base(SubstitutionEnvironment):
def CacheDir(self, path):
import SCons.CacheDir
- if path is None:
- self._CacheDir = SCons.CacheDir.Null()
- else:
- self._CacheDir = SCons.CacheDir.CacheDir(self.subst(path))
+ if not path is None:
+ path = self.subst(path)
+ self._CacheDir_path = path
def Clean(self, targets, files):
global CleanTargets
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index 3f64d43c..4ffff7ad 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -576,13 +576,13 @@ class SubstitutionTestCase(unittest.TestCase):
BAR=StringableObj("bar"))
r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ])
- assert r == [ "foo/bar", "bar/baz" ]
+ assert r == [ "foo/bar", "bar/baz" ], r
r = env.subst_path([ "bar/${FOO}", "baz/${BAR}" ])
- assert r == [ "bar/foo", "baz/bar" ]
+ assert r == [ "bar/foo", "baz/bar" ], r
r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ])
- assert r == [ "bar/foo/bar", "baz/bar/baz" ]
+ assert r == [ "bar/foo/bar", "baz/bar/baz" ], r
def test_subst_target_source(self):
"""Test the base environment subst_target_source() method"""
@@ -764,40 +764,6 @@ sys.exit(1)
d = env.ParseFlags(s)
- if sys.version[:3] in ('1.5', '1.6', '2.0', '2.1', '2.2'):
- # Pre-2.3 Python has no shlex.split() function.
- # The compatibility layer does its best can by wrapping
- # the old shlex.shlex class, but that class doesn't really
- # understand quoting within the body of a token. We're just
- # going to live with this; it's the behavior they'd
- # have anyway if they use the shlex module...
- #
- # (Note that we must test the actual Python version numbers
- # above, not just test for whether trying to use shlex.split()
- # throws an AttributeError, because the compatibility layer
- # adds our wrapper function to the module as shlex.split().)
-
- expect_CPPPATH = ['/usr/include/fum',
- 'bar',
- '"C:\\Program']
- expect_LIBPATH = ['/usr/fax',
- 'foo',
- '"C:\\Program']
- expect_LIBS = ['Files\\ASCEND\\include"',
- 'xxx',
- 'yyy',
- 'Files\\ASCEND"',
- 'ascend']
- else:
- expect_CPPPATH = ['/usr/include/fum',
- 'bar',
- 'C:\\Program Files\\ASCEND\\include']
- expect_LIBPATH = ['/usr/fax',
- 'foo',
- 'C:\\Program Files\\ASCEND']
- expect_LIBS = ['xxx', 'yyy', 'ascend']
-
-
assert d['ASFLAGS'] == ['-as'], d['ASFLAGS']
assert d['CFLAGS'] == ['-std=c99']
assert d['CCFLAGS'] == ['-X', '-Wa,-as',
@@ -806,12 +772,16 @@ sys.exit(1)
'+DD64'], d['CCFLAGS']
assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES']
assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS']
- assert d['CPPPATH'] == expect_CPPPATH, d['CPPPATH']
+ assert d['CPPPATH'] == ['/usr/include/fum',
+ 'bar',
+ 'C:\\Program Files\\ASCEND\\include'], d['CPPPATH']
assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH']
assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS']
- assert d['LIBPATH'] == expect_LIBPATH, d['LIBPATH']
+ assert d['LIBPATH'] == ['/usr/fax',
+ 'foo',
+ 'C:\\Program Files\\ASCEND'], d['LIBPATH']
LIBS = map(str, d['LIBS'])
- assert LIBS == expect_LIBS, (d['LIBS'], LIBS)
+ assert LIBS == ['xxx', 'yyy', 'ascend'], (d['LIBS'], LIBS)
assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread',
'-mno-cygwin', '-mwindows',
('-arch', 'i386'),
@@ -2589,10 +2559,10 @@ def generate(env):
env = self.TestEnvironment(CD = 'CacheDir')
env.CacheDir('foo')
- assert env._CacheDir.path == 'foo', env._CacheDir.path
+ assert env._CacheDir_path == 'foo', env._CacheDir_path
env.CacheDir('$CD')
- assert env._CacheDir.path == 'CacheDir', env._CacheDir.path
+ assert env._CacheDir_path == 'CacheDir', env._CacheDir_path
def test_Clean(self):
"""Test the Clean() method"""
diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py
index 1cb0cf97..72220424 100644
--- a/src/engine/SCons/Executor.py
+++ b/src/engine/SCons/Executor.py
@@ -335,13 +335,11 @@ class Null(_Executor):
def get_build_env(self):
import SCons.Util
class NullEnvironment(SCons.Util.Null):
- #def get_scanner(self, key):
- # return None
- #def changed_since_last_build(self, dependency, target, prev_ni):
- # return dependency.changed_since_last_buld(target, prev_ni)
+ import SCons.CacheDir
+ _CacheDir_path = None
+ _CacheDir = SCons.CacheDir.CacheDir(None)
def get_CacheDir(self):
- import SCons.CacheDir
- return SCons.CacheDir.Null()
+ return self._CacheDir
return NullEnvironment()
def get_build_scanner_path(self):
return None
diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py
index b28aaaff..7b514092 100644
--- a/src/engine/SCons/Job.py
+++ b/src/engine/SCons/Job.py
@@ -33,6 +33,18 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
+
+# The default stack size (in kilobytes) of the threads used to execute
+# jobs in parallel.
+#
+# We use a stack size of 256 kilobytes. The default on some platforms
+# is too large and prevents us from creating enough threads to fully
+# parallelized the build. For example, the default stack size on linux
+# is 8 MBytes.
+
+default_stack_size = 256
+
+
class Jobs:
"""An instance of this class initializes N jobs, and provides
methods for starting, stopping, and waiting on all N jobs.
@@ -55,7 +67,12 @@ class Jobs:
self.job = None
if num > 1:
try:
- self.job = Parallel(taskmaster, num)
+ stack_size = SCons.Job.stack_size
+ except AttributeError:
+ stack_size = default_stack_size
+
+ try:
+ self.job = Parallel(taskmaster, num, stack_size)
self.num_jobs = num
except NameError:
pass
@@ -175,17 +192,40 @@ else:
class ThreadPool:
"""This class is responsible for spawning and managing worker threads."""
- def __init__(self, num):
- """Create the request and reply queues, and 'num' worker threads."""
+ def __init__(self, num, stack_size):
+ """Create the request and reply queues, and 'num' worker threads.
+
+ One must specify the stack size of the worker threads. The
+ stack size is specified in kilobytes.
+ """
self.requestQueue = Queue.Queue(0)
self.resultsQueue = Queue.Queue(0)
+ try:
+ prev_size = threading.stack_size(stack_size*1024)
+ except AttributeError, e:
+ # Only print a warning if the stack size has been
+ # explicitely set.
+ if hasattr(SCons.Job, 'stack_size'):
+ msg = "Setting stack size is unsupported by this version of Python:\n " + \
+ e.args[0]
+ SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
+ except ValueError, e:
+ msg = "Setting stack size failed:\n " + \
+ e.message
+ SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
+
# Create worker threads
self.workers = []
for _ in range(num):
worker = Worker(self.requestQueue, self.resultsQueue)
self.workers.append(worker)
+ # Once we drop Python 1.5 we can change the following to:
+ #if 'prev_size' in locals():
+ if 'prev_size' in locals().keys():
+ threading.stack_size(prev_size)
+
def put(self, obj):
"""Put task into request queue."""
self.requestQueue.put(obj)
@@ -233,7 +273,7 @@ else:
This class is thread safe.
"""
- def __init__(self, taskmaster, num):
+ def __init__(self, taskmaster, num, stack_size):
"""Create a new parallel job given a taskmaster.
The taskmaster's next_task() method should return the next
@@ -249,7 +289,7 @@ else:
multiple tasks simultaneously. """
self.taskmaster = taskmaster
- self.tp = ThreadPool(num)
+ self.tp = ThreadPool(num, stack_size)
self.maxjobs = num
diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py
index 5f056e86..c4325812 100644
--- a/src/engine/SCons/JobTests.py
+++ b/src/engine/SCons/JobTests.py
@@ -293,7 +293,7 @@ class SerialTestCase(unittest.TestCase):
class NoParallelTestCase(unittest.TestCase):
def runTest(self):
"test handling lack of parallel support"
- def NoParallel(tm, num):
+ def NoParallel(tm, num, stack_size):
raise NameError
save_Parallel = SCons.Job.Parallel
SCons.Job.Parallel = NoParallel
diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py
index c2b41818..c4a5001e 100644
--- a/src/engine/SCons/Memoize.py
+++ b/src/engine/SCons/Memoize.py
@@ -217,33 +217,47 @@ class Memoizer:
class M:
def __init__(cls, name, bases, cls_dict):
- cls.has_metaclass = 1
-
-class A:
- __metaclass__ = M
+ cls.use_metaclass = 1
+ def fake_method(self):
+ pass
+ new.instancemethod(fake_method, None, cls)
try:
- has_metaclass = A.has_metaclass
+ class A:
+ __metaclass__ = M
+
+ use_metaclass = A.use_metaclass
except AttributeError:
- has_metaclass = None
+ use_metaclass = None
+ reason = 'no metaclasses'
+except TypeError:
+ use_metaclass = None
+ reason = 'new.instancemethod() bug'
+else:
+ del A
del M
-del A
-if not has_metaclass:
+if not use_metaclass:
def Dump(title):
pass
- class Memoized_Metaclass:
- # Just a place-holder so pre-metaclass Python versions don't
- # have to have special code for the Memoized classes.
- pass
+ try:
+ class Memoized_Metaclass(type):
+ # Just a place-holder so pre-metaclass Python versions don't
+ # have to have special code for the Memoized classes.
+ pass
+ except TypeError:
+ class Memoized_Metaclass:
+ # A place-holder so pre-metaclass Python versions don't
+ # have to have special code for the Memoized classes.
+ pass
def EnableMemoization():
import SCons.Warnings
- msg = 'memoization is not supported in this version of Python (no metaclasses)'
- raise SCons.Warnings.NoMetaclassSupportWarning, msg
+ msg = 'memoization is not supported in this version of Python (%s)'
+ raise SCons.Warnings.NoMetaclassSupportWarning, msg % reason
else:
diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py
index 7102f305..bceeebf8 100644
--- a/src/engine/SCons/MemoizeTests.py
+++ b/src/engine/SCons/MemoizeTests.py
@@ -132,7 +132,7 @@ class CountDictTestCase(unittest.TestCase):
c = obj.get_memoizer_counter('dict')
- if SCons.Memoize.has_metaclass:
+ if SCons.Memoize.use_metaclass:
assert c.hit == 3, c.hit
assert c.miss == 2, c.miss
else:
@@ -171,7 +171,7 @@ class CountValueTestCase(unittest.TestCase):
c = obj.get_memoizer_counter('value')
- if SCons.Memoize.has_metaclass:
+ if SCons.Memoize.use_metaclass:
assert c.hit == 3, c.hit
assert c.miss == 1, c.miss
else:
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index d0843d1f..1a3c0100 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -2469,38 +2469,20 @@ class File(Base):
self.get_build_env().get_CacheDir().push_if_forced(self)
ninfo = self.get_ninfo()
- old = self.get_stored_info()
-
- csig = None
- mtime = self.get_timestamp()
- size = self.get_size()
-
- max_drift = self.fs.max_drift
- if max_drift > 0:
- if (time.time() - mtime) > max_drift:
- try:
- n = old.ninfo
- if n.timestamp and n.csig and n.timestamp == mtime:
- csig = n.csig
- except AttributeError:
- pass
- elif max_drift == 0:
- try:
- csig = old.ninfo.csig
- except AttributeError:
- pass
+ csig = self.get_max_drift_csig()
if csig:
ninfo.csig = csig
- ninfo.timestamp = mtime
- ninfo.size = size
+ ninfo.timestamp = self.get_timestamp()
+ ninfo.size = self.get_size()
if not self.has_builder():
# This is a source file, but it might have been a target file
# in another build that included more of the DAG. Copy
# any build information that's stored in the .sconsign file
# into our binfo object so it doesn't get lost.
+ old = self.get_stored_info()
self.get_binfo().__dict__.update(old.binfo.__dict__)
self.store_info()
@@ -2638,6 +2620,33 @@ class File(Base):
# SIGNATURE SUBSYSTEM
#
+ def get_max_drift_csig(self):
+ """
+ Returns the content signature currently stored for this node
+ if it's been unmodified longer than the max_drift value, or the
+ max_drift value is 0. Returns None otherwise.
+ """
+ old = self.get_stored_info()
+ mtime = self.get_timestamp()
+
+ csig = None
+ max_drift = self.fs.max_drift
+ if max_drift > 0:
+ if (time.time() - mtime) > max_drift:
+ try:
+ n = old.ninfo
+ if n.timestamp and n.csig and n.timestamp == mtime:
+ csig = n.csig
+ except AttributeError:
+ pass
+ elif max_drift == 0:
+ try:
+ csig = old.ninfo.csig
+ except AttributeError:
+ pass
+
+ return csig
+
def get_csig(self):
"""
Generate a node's content signature, the digested signature
@@ -2653,16 +2662,19 @@ class File(Base):
except AttributeError:
pass
- try:
- contents = self.get_contents()
- except IOError:
- # This can happen if there's actually a directory on-disk,
- # which can be the case if they've disabled disk checks,
- # or if an action with a File target actually happens to
- # create a same-named directory by mistake.
- csig = ''
- else:
- csig = SCons.Util.MD5signature(contents)
+ csig = self.get_max_drift_csig()
+ if csig is None:
+
+ try:
+ contents = self.get_contents()
+ except IOError:
+ # This can happen if there's actually a directory on-disk,
+ # which can be the case if they've disabled disk checks,
+ # or if an action with a File target actually happens to
+ # create a same-named directory by mistake.
+ csig = ''
+ else:
+ csig = SCons.Util.MD5signature(contents)
ninfo.csig = csig
@@ -2842,14 +2854,14 @@ class FileFinder:
It would be more compact to just use this as a nested function
with a default keyword argument (see the commented-out version
below), but that doesn't work unless you have nested scopes,
- so we define it here just this works work under Python 1.5.2.
+ so we define it here just so this work under Python 1.5.2.
"""
if fd is None:
fd = self.default_filedir
dir, name = os.path.split(fd)
drive, d = os.path.splitdrive(dir)
if d in ('/', os.sep):
- return p
+ return p.fs.get_root(drive).dir_on_disk(name)
if dir:
p = self.filedir_lookup(p, dir)
if not p:
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index fe42035d..8e9a3f8d 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -630,13 +630,13 @@ class NodeTestCase(unittest.TestCase):
# XXX additional tests for the guts of the functionality some day
- def test_del_binfo(self):
- """Test deleting the build information from a Node
- """
- node = SCons.Node.Node()
- node.binfo = None
- node.del_binfo()
- assert not hasattr(node, 'binfo'), node
+ #def test_del_binfo(self):
+ # """Test deleting the build information from a Node
+ # """
+ # node = SCons.Node.Node()
+ # node.binfo = None
+ # node.del_binfo()
+ # assert not hasattr(node, 'binfo'), node
def test_store_info(self):
"""Test calling the method to store build information
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index f2521515..4ca34e01 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -375,7 +375,6 @@ class Node:
# waiting for this Node to be built.
for parent in self.waiting_parents.keys():
parent.implicit = None
- parent.del_binfo()
self.clear()
@@ -433,14 +432,10 @@ class Node:
can be re-evaluated by interfaces that do continuous integration
builds).
"""
- # Note in case it's important in the future: We also used to clear
- # the build information (the lists of dependencies) here like this:
- #
- # self.del_binfo()
- #
- # But we now rely on the fact that we're going to look at that
- # once before the build, and then store the results in the
- # .sconsign file after the build.
+ # The del_binfo() call here isn't necessary for normal execution,
+ # but is for interactive mode, where we might rebuild the same
+ # target and need to start from scratch.
+ self.del_binfo()
self.clear_memoized_values()
self.ninfo = self.new_ninfo()
self.executor_cleanup()
@@ -639,8 +634,6 @@ class Node:
# so we must recalculate the implicit deps:
self.implicit = []
self.implicit_dict = {}
- self._children_reset()
- self.del_binfo()
# Have the executor scan the sources.
executor.scan_sources(self.builder.source_scanner)
@@ -1013,6 +1006,7 @@ class Node:
# entries to equal the new dependency list, for the benefit
# of the loop below that updates node information.
then.extend([None] * diff)
+ if t: Trace(': old %s new %s' % (len(then), len(children)))
result = True
for child, prev_ni in zip(children, then):
diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py
index be645ca2..ae00fc0d 100644
--- a/src/engine/SCons/PathList.py
+++ b/src/engine/SCons/PathList.py
@@ -59,7 +59,7 @@ def node_conv(obj):
try:
get = obj.get
except AttributeError:
- if isinstance(obj, SCons.Node.Node):
+ if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ):
result = obj
else:
result = str(obj)
@@ -132,10 +132,9 @@ class _PathList:
value = env.subst(value, target=target, source=source,
conv=node_conv)
if SCons.Util.is_Sequence(value):
- # It came back as a string or tuple, which in this
- # case usually means some variable expanded to an
- # actually Dir node. Concatenate the values.
- value = string.join(map(str, value), '')
+ result.extend(value)
+ continue
+
elif type == TYPE_OBJECT:
value = node_conv(value)
if value:
diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py
index 1d4e9f70..afdabe1a 100644
--- a/src/engine/SCons/Platform/posix.py
+++ b/src/engine/SCons/Platform/posix.py
@@ -235,7 +235,7 @@ def generate(env):
env['LIBSUFFIX'] = '.a'
env['SHLIBPREFIX'] = '$LIBPREFIX'
env['SHLIBSUFFIX'] = '.so'
- env['LIBPREFIXES'] = '$LIBPREFIX'
+ env['LIBPREFIXES'] = [ '$LIBPREFIX' ]
env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
env['PSPAWN'] = pspawn
env['SPAWN'] = spawn
diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py
index ae3a77eb..c5de4988 100644
--- a/src/engine/SCons/SConf.py
+++ b/src/engine/SCons/SConf.py
@@ -404,11 +404,12 @@ class SConfBase:
'CheckFunc' : CheckFunc,
'CheckType' : CheckType,
'CheckTypeSize' : CheckTypeSize,
+ 'CheckDeclaration' : CheckDeclaration,
'CheckHeader' : CheckHeader,
'CheckCHeader' : CheckCHeader,
'CheckCXXHeader' : CheckCXXHeader,
'CheckLib' : CheckLib,
- 'CheckLibWithHeader' : CheckLibWithHeader
+ 'CheckLibWithHeader' : CheckLibWithHeader,
}
self.AddTests(default_tests)
self.AddTests(custom_tests)
@@ -425,6 +426,31 @@ class SConfBase:
self._shutdown()
return self.env
+ def Define(self, name, value = None, comment = None):
+ """
+ Define a pre processor symbol name, with the optional given value in the
+ current config header.
+
+ If value is None (default), then #define name is written. If value is not
+ none, then #define name value is written.
+
+ comment is a string which will be put as a C comment in the
+ header, to explain the meaning of the value (appropriate C comments /* and
+ */ will be put automatically."""
+ lines = []
+ if comment:
+ comment_str = "/* %s */" % comment
+ lines.append(comment_str)
+
+ if value is not None:
+ define_str = "#define %s %s" % (name, value)
+ else:
+ define_str = "#define %s" % name
+ lines.append(define_str)
+ lines.append('')
+
+ self.config_h_text = self.config_h_text + string.join(lines, '\n')
+
def BuildNodes(self, nodes):
"""
Tries to build the given nodes immediately. Returns 1 on success,
@@ -797,6 +823,12 @@ class CheckContext:
# TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
return not self.TryBuild(self.env.Object, text, ext)
+ def RunProg(self, text, ext):
+ self.sconf.cached = 1
+ # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
+ st, out = self.TryRun(text, ext)
+ return not st, out
+
def AppendLIBS(self, lib_name_list):
oldLIBS = self.env.get( 'LIBS', [] )
self.env.Append(LIBS = lib_name_list)
@@ -855,6 +887,13 @@ def CheckTypeSize(context, type_name, includes = "", language = None, expect = N
context.did_show_result = 1
return res
+def CheckDeclaration(context, declaration, includes = "", language = None):
+ res = SCons.Conftest.CheckDeclaration(context, declaration,
+ includes = includes,
+ language = language)
+ context.did_show_result = 1
+ return not res
+
def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
# used by CheckHeader and CheckLibWithHeader to produce C - #include
# statements from the specified header (list)
diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py
index 601c5eb4..f7d33f8e 100644
--- a/src/engine/SCons/SConfTests.py
+++ b/src/engine/SCons/SConfTests.py
@@ -497,6 +497,42 @@ int main() {
finally:
sconf.Finish()
+ def test_Define(self):
+ """Test SConf.Define()
+ """
+ self._resetSConfState()
+ sconf = self.SConf.SConf(self.scons_env,
+ conf_dir=self.test.workpath('config.tests'),
+ log_file=self.test.workpath('config.log'),
+ config_h = self.test.workpath('config.h'))
+ try:
+ # XXX: we test the generated config.h string. This is not so good,
+ # ideally, we would like to test if the generated file included in
+ # a test program does what we want.
+
+ # Test defining one symbol wo value
+ sconf.config_h_text = ''
+ sconf.Define('YOP')
+ assert sconf.config_h_text == '#define YOP\n'
+
+ # Test defining one symbol with integer value
+ sconf.config_h_text = ''
+ sconf.Define('YOP', 1)
+ assert sconf.config_h_text == '#define YOP 1\n'
+
+ # Test defining one symbol with string value
+ sconf.config_h_text = ''
+ sconf.Define('YOP', '"YIP"')
+ assert sconf.config_h_text == '#define YOP "YIP"\n'
+
+ # Test defining one symbol with string value
+ sconf.config_h_text = ''
+ sconf.Define('YOP', "YIP")
+ assert sconf.config_h_text == '#define YOP YIP\n'
+
+ finally:
+ sconf.Finish()
+
def test_CheckTypeSize(self):
"""Test SConf.CheckTypeSize()
"""
@@ -531,6 +567,25 @@ int main() {
finally:
sconf.Finish()
+ def test_CheckDeclaration(self):
+ """Test SConf.CheckDeclaration()
+ """
+ self._resetSConfState()
+ sconf = self.SConf.SConf(self.scons_env,
+ conf_dir=self.test.workpath('config.tests'),
+ log_file=self.test.workpath('config.log'))
+ try:
+ # In ANSI C, malloc should be available in stdlib
+ r = sconf.CheckDeclaration('malloc', includes = "#include <stdlib.h>")
+ assert r, "malloc not declared ??"
+ # For C++, __cplusplus should be declared
+ r = sconf.CheckDeclaration('__cplusplus', language = 'C++')
+ assert r, "__cplusplus not declared in C++ ??"
+ r = sconf.CheckDeclaration('__cplusplus', language = 'C')
+ assert not r, "__cplusplus declared in C ??"
+ finally:
+ sconf.Finish()
+
def test_(self):
"""Test SConf.CheckType()
"""
diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py
index 276570e0..4356c7a4 100644
--- a/src/engine/SCons/Scanner/C.py
+++ b/src/engine/SCons/Scanner/C.py
@@ -31,10 +31,94 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node.FS
import SCons.Scanner
+import SCons.Util
+
+import SCons.cpp
+
+class SConsCPPScanner(SCons.cpp.PreProcessor):
+ """
+ SCons-specific subclass of the cpp.py module's processing.
+
+ We subclass this so that: 1) we can deal with files represented
+ by Nodes, not strings; 2) we can keep track of the files that are
+ missing.
+ """
+ def __init__(self, *args, **kw):
+ apply(SCons.cpp.PreProcessor.__init__, (self,)+args, kw)
+ self.missing = []
+ def initialize_result(self, fname):
+ self.result = SCons.Util.UniqueList([fname])
+ def finalize_result(self, fname):
+ return self.result[1:]
+ def find_include_file(self, t):
+ keyword, quote, fname = t
+ result = SCons.Node.FS.find_file(fname, self.searchpath[quote])
+ if not result:
+ self.missing.append((fname, self.current_file))
+ return result
+ def read_file(self, file):
+ try:
+ fp = open(str(file.rfile()))
+ except EnvironmentError, e:
+ self.missing.append((file, self.current_file))
+ return ''
+ else:
+ return fp.read()
+
+def dictify_CPPDEFINES(env):
+ cppdefines = env.get('CPPDEFINES', {})
+ if cppdefines is None:
+ return {}
+ if SCons.Util.is_Sequence(cppdefines):
+ result = {}
+ for c in cppdefines:
+ if SCons.Util.is_Sequence(c):
+ result[c[0]] = c[1]
+ else:
+ result[c] = None
+ return result
+ if not SCons.Util.is_Dict(cppdefines):
+ return {cppdefines : None}
+ return cppdefines
+
+class SConsCPPScannerWrapper:
+ """
+ The SCons wrapper around a cpp.py scanner.
+
+ This is the actual glue between the calling conventions of generic
+ SCons scanners, and the (subclass of) cpp.py class that knows how
+ to look for #include lines with reasonably real C-preprocessor-like
+ evaluation of #if/#ifdef/#else/#elif lines.
+ """
+ def __init__(self, name, variable):
+ self.name = name
+ self.path = SCons.Scanner.FindPathDirs(variable)
+ def __call__(self, node, env, path = ()):
+ cpp = SConsCPPScanner(current = node.get_dir(),
+ cpppath = path,
+ dict = dictify_CPPDEFINES(env))
+ result = cpp(node)
+ for included, includer in cpp.missing:
+ fmt = "No dependency generated for file: %s (included from: %s) -- file not found"
+ SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
+ fmt % (included, includer))
+ return result
+
+ def recurse_nodes(self, nodes):
+ return nodes
+ def select(self, node):
+ return self
def CScanner():
"""Return a prototype Scanner instance for scanning source files
that use the C pre-processor"""
+
+ # Here's how we would (or might) use the CPP scanner code above that
+ # knows how to evaluate #if/#ifdef/#else/#elif lines when searching
+ # for #includes. This is commented out for now until we add the
+ # right configurability to let users pick between the scanners.
+ #return SConsCPPScannerWrapper("CScanner", "CPPPATH")
+
cs = SCons.Scanner.ClassicCPP("CScanner",
"$CPPSUFFIXES",
"CPPPATH",
diff --git a/src/engine/SCons/Scanner/D.py b/src/engine/SCons/Scanner/D.py
index 5a0b3834..bfbcd5de 100644
--- a/src/engine/SCons/Scanner/D.py
+++ b/src/engine/SCons/Scanner/D.py
@@ -32,22 +32,37 @@ Coded by Andy Friesen
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import re
import string
import SCons.Scanner
def DScanner():
"""Return a prototype Scanner instance for scanning D source files"""
- ds = D(name = "DScanner",
- suffixes = '$DSUFFIXES',
- path_variable = 'DPATH',
- regex = 'import\s+([^\;]*)\;')
+ ds = D()
return ds
class D(SCons.Scanner.Classic):
+ def __init__ (self):
+ SCons.Scanner.Classic.__init__ (self,
+ name = "DScanner",
+ suffixes = '$DSUFFIXES',
+ path_variable = 'DPATH',
+ regex = 'import\s+(?:[a-zA-Z0-9_.]+)\s*(?:,\s*(?:[a-zA-Z0-9_.]+)\s*)*;')
+
+ self.cre2 = re.compile ('(?:import\s)?\s*([a-zA-Z0-9_.]+)\s*(?:,|;)', re.M)
+
def find_include(self, include, source_dir, path):
# translate dots (package separators) to slashes
inc = string.replace(include, '.', '/')
i = SCons.Node.FS.find_file(inc + '.d', (source_dir,) + path)
+ if i is None:
+ i = SCons.Node.FS.find_file (inc + '.di', (source_dir,) + path)
return i, include
+
+ def find_include_names(self, node):
+ includes = []
+ for i in self.cre.findall(node.get_contents()):
+ includes = includes + self.cre2.findall(i)
+ return includes
diff --git a/src/engine/SCons/Scanner/LaTeX.py b/src/engine/SCons/Scanner/LaTeX.py
index c0a38b56..ceb9bf52 100644
--- a/src/engine/SCons/Scanner/LaTeX.py
+++ b/src/engine/SCons/Scanner/LaTeX.py
@@ -56,7 +56,7 @@ class LaTeX(SCons.Scanner.Classic):
but leave the file name untouched for "includegraphics." For
the "bibliography" keyword we need to add .bib if there is
no extension. (This need to be revisited since if there
- is no extension for an :includegraphics" keyword latex will
+ is no extension for an "includegraphics" keyword latex will
append .ps or .eps to find the file; while pdftex will use
other extensions.)
"""
diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py
index 64d6d77d..6e9286a4 100644
--- a/src/engine/SCons/Scanner/ScannerTests.py
+++ b/src/engine/SCons/Scanner/ScannerTests.py
@@ -70,9 +70,12 @@ class FindPathDirsTestCase(unittest.TestCase):
env = DummyEnvironment(LIBPATH = [ 'foo' ])
env.fs = DummyFS()
+ env.fs._cwd = DummyNode('cwd')
dir = DummyNode('dir', ['xxx'])
fpd = SCons.Scanner.FindPathDirs('LIBPATH')
+ result = fpd(env)
+ assert str(result) == "('foo',)", result
result = fpd(env, dir)
assert str(result) == "('xxx', 'foo')", result
diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py
index c8ab1557..924b2716 100644
--- a/src/engine/SCons/Scanner/__init__.py
+++ b/src/engine/SCons/Scanner/__init__.py
@@ -67,7 +67,7 @@ class FindPathDirs:
will return all of the *path directories."""
def __init__(self, variable):
self.variable = variable
- def __call__(self, env, dir, target=None, source=None, argument=None):
+ def __call__(self, env, dir=None, target=None, source=None, argument=None):
import SCons.PathList
try:
path = env[self.variable]
@@ -346,13 +346,16 @@ class Classic(Current):
def sort_key(self, include):
return SCons.Node.FS._my_normcase(include)
+ def find_include_names(self, node):
+ return self.cre.findall(node.get_contents())
+
def scan(self, node, path=()):
# cache the includes list in node so we only scan it once:
if node.includes != None:
includes = node.includes
else:
- includes = self.cre.findall(node.get_contents())
+ includes = self.find_include_names (node)
node.includes = includes
# This is a hand-coded DSU (decorate-sort-undecorate, or
diff --git a/src/engine/SCons/Script/Interactive.py b/src/engine/SCons/Script/Interactive.py
new file mode 100644
index 00000000..e38c4002
--- /dev/null
+++ b/src/engine/SCons/Script/Interactive.py
@@ -0,0 +1,359 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+__doc__ = """
+SCons interactive mode
+"""
+
+# TODO:
+#
+# This has the potential to grow into something with a really big life
+# of its own, which might or might not be a good thing. Nevertheless,
+# here are some enhancements that will probably be requested some day
+# and are worth keeping in mind (assuming this takes off):
+#
+# - A command to re-read / re-load the SConscript files. This may
+# involve allowing people to specify command-line options (e.g. -f,
+# -I, --no-site-dir) that affect how the SConscript files are read.
+#
+# - Additional command-line options on the "build" command.
+#
+# Of the supported options that seemed to make sense (after a quick
+# pass through the list), the ones that seemed likely enough to be
+# used are listed in the man page and have explicit test scripts.
+#
+# These had code changed in Script/Main.py to support them, but didn't
+# seem likely to be used regularly, so had no test scripts added:
+#
+# build --diskcheck=*
+# build --implicit-cache=*
+# build --implicit-deps-changed=*
+# build --implicit-deps-unchanged=*
+#
+# These look like they should "just work" with no changes to the
+# existing code, but like those above, look unlikely to be used and
+# therefore had no test scripts added:
+#
+# build --random
+#
+# These I'm not sure about. They might be useful for individual
+# "build" commands, and may even work, but they seem unlikely enough
+# that we'll wait until they're requested before spending any time on
+# writing test scripts for them, or investigating whether they work.
+#
+# build -q [??? is there a useful analog to the exit status?]
+# build --duplicate=
+# build --profile=
+# build --max-drift=
+# build --warn=*
+# build --Y
+#
+# - Most of the SCons command-line options that the "build" command
+# supports should be settable as default options that apply to all
+# subsequent "build" commands. Maybe a "set {option}" command that
+# maps to "SetOption('{option}')".
+#
+# - Need something in the 'help' command that prints the -h output.
+#
+# - A command to run the configure subsystem separately (must see how
+# this interacts with the new automake model).
+#
+# - Command-line completion of target names; maybe even of SCons options?
+# Completion is something that's supported by the Python cmd module,
+# so this should be doable without too much trouble.
+#
+
+import cmd
+import copy
+import os
+import re
+import shlex
+import string
+import sys
+
+try:
+ import readline
+except ImportError:
+ pass
+
+from SCons.Debug import Trace
+
+class SConsInteractiveCmd(cmd.Cmd):
+ """\
+ build [TARGETS] Build the specified TARGETS and their dependencies.
+ 'b' is a synonym.
+ clean [TARGETS] Clean (remove) the specified TARGETS and their
+ dependencies. 'c' is a synonym.
+ exit Exit SCons interactive mode.
+ help [COMMAND] Prints help for the specified COMMAND. 'h' and
+ '?' are synonyms.
+ shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!'
+ are synonyms.
+ version Prints SCons version information.
+ """
+
+ synonyms = {
+ 'b' : 'build',
+ 'c' : 'clean',
+ 'h' : 'help',
+ 'scons' : 'build',
+ 'sh' : 'shell',
+ }
+
+ def __init__(self, **kw):
+ cmd.Cmd.__init__(self)
+ for key, val in kw.items():
+ setattr(self, key, val)
+
+ if sys.platform == 'win32':
+ self.shell_variable = 'COMSPEC'
+ else:
+ self.shell_variable = 'SHELL'
+
+ def default(self, argv):
+ print "*** Unknown command: %s" % argv[0]
+
+ def onecmd(self, line):
+ line = string.strip(line)
+ if not line:
+ print self.lastcmd
+ return self.emptyline()
+ self.lastcmd = line
+ if line[0] == '!':
+ line = 'shell ' + line[1:]
+ elif line[0] == '?':
+ line = 'help ' + line[1:]
+ argv = shlex.split(line)
+ argv[0] = self.synonyms.get(argv[0], argv[0])
+ if not argv[0]:
+ return self.default(line)
+ else:
+ try:
+ func = getattr(self, 'do_' + argv[0])
+ except AttributeError:
+ return self.default(argv)
+ return func(argv)
+
+ def do_build(self, argv):
+ """\
+ build [TARGETS] Build the specified TARGETS and their
+ dependencies. 'b' is a synonym.
+ """
+ import SCons.SConsign
+ import SCons.Script.Main
+
+ options = copy.deepcopy(self.options)
+
+ options, targets = self.parser.parse_args(argv[1:], values=options)
+
+ SCons.Script.COMMAND_LINE_TARGETS = targets
+
+ if targets:
+ SCons.Script.BUILD_TARGETS = targets
+ else:
+ # If the user didn't specify any targets on the command line,
+ # use the list of default targets.
+ SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default
+
+ nodes = SCons.Script.Main._build_targets(self.fs,
+ options,
+ targets,
+ self.target_top)
+
+ if not nodes:
+ return
+
+ # Clean up so that we can perform the next build correctly.
+ #
+ # We do this by walking over all the children of the targets,
+ # and clearing their state.
+ #
+ # We currently have to re-scan each node to find their
+ # children, because built nodes have already been partially
+ # cleared and don't remember their children. (In scons
+ # 0.96.1 and earlier, this wasn't the case, and we didn't
+ # have to re-scan the nodes.)
+ #
+ # Because we have to re-scan each node, we can't clear the
+ # nodes as we walk over them, because we may end up rescanning
+ # a cleared node as we scan a later node. Therefore, only
+ # store the list of nodes that need to be cleared as we walk
+ # the tree, and clear them in a separate pass.
+ #
+ # XXX: Someone more familiar with the inner workings of scons
+ # may be able to point out a more efficient way to do this.
+
+ SCons.Script.Main.progress_display("scons: Clearing cached node information ...")
+
+ seen_nodes = {}
+
+ def get_unseen_children(node, parent, seen_nodes=seen_nodes):
+ def is_unseen(node, seen_nodes=seen_nodes):
+ return not seen_nodes.has_key(node)
+ return filter(is_unseen, node.children(scan=1))
+
+ def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
+ seen_nodes[node] = 1
+
+ # If this file is in a BuildDir and has a
+ # corresponding source file in the source tree, remember the
+ # node in the source tree, too. This is needed in
+ # particular to clear cached implicit dependencies on the
+ # source file, since the scanner will scan it if the
+ # BuildDir was created with duplicate=0.
+ try:
+ rfile_method = node.rfile
+ except AttributeError:
+ return
+ else:
+ rfile = rfile_method()
+ if rfile != node:
+ seen_nodes[rfile] = 1
+
+ for node in nodes:
+ walker = SCons.Node.Walker(node,
+ kids_func=get_unseen_children,
+ eval_func=add_to_seen_nodes)
+ n = walker.next()
+ while n:
+ n = walker.next()
+
+ for node in seen_nodes.keys():
+ # Call node.clear() to clear most of the state
+ node.clear()
+ # node.clear() doesn't reset node.state, so call
+ # node.set_state() to reset it manually
+ node.set_state(SCons.Node.no_state)
+ node.implicit = None
+
+ SCons.SConsign.Reset()
+ SCons.Script.Main.progress_display("scons: done clearing node information.")
+
+ def do_clean(self, argv):
+ """\
+ clean [TARGETS] Clean (remove) the specified TARGETS
+ and their dependencies. 'c' is a synonym.
+ """
+ return self.do_build(['build', '--clean'] + argv[1:])
+
+ def do_EOF(self, argv):
+ print
+ self.do_exit(argv)
+
+ def _do_one_help(self, arg):
+ try:
+ # If help_<arg>() exists, then call it.
+ func = getattr(self, 'help_' + arg)
+ except AttributeError:
+ try:
+ func = getattr(self, 'do_' + arg)
+ except AttributeError:
+ doc = None
+ else:
+ doc = self._doc_to_help(func)
+ if doc:
+ sys.stdout.write(doc + '\n')
+ sys.stdout.flush()
+ else:
+ doc = self.strip_initial_spaces(func())
+ if doc:
+ sys.stdout.write(doc + '\n')
+ sys.stdout.flush()
+
+ def _doc_to_help(self, obj):
+ doc = obj.__doc__
+ if doc is None:
+ return ''
+ return self._strip_initial_spaces(doc)
+
+ def _strip_initial_spaces(self, s):
+ #lines = s.split('\n')
+ lines = string.split(s, '\n')
+ spaces = re.match(' *', lines[0]).group(0)
+ #def strip_spaces(l):
+ # if l.startswith(spaces):
+ # l = l[len(spaces):]
+ # return l
+ #return '\n'.join([ strip_spaces(l) for l in lines ])
+ def strip_spaces(l, spaces=spaces):
+ if l[:len(spaces)] == spaces:
+ l = l[len(spaces):]
+ return l
+ lines = map(strip_spaces, lines)
+ return string.join(lines, '\n')
+
+ def do_exit(self, argv):
+ """\
+ exit Exit SCons interactive mode.
+ """
+ sys.exit(0)
+
+ def do_help(self, argv):
+ """\
+ help [COMMAND] Prints help for the specified COMMAND. 'h'
+ and '?' are synonyms.
+ """
+ if argv[1:]:
+ for arg in argv[1:]:
+ if self._do_one_help(arg):
+ break
+ else:
+ # If bare 'help' is called, print this class's doc
+ # string (if it has one).
+ doc = self._doc_to_help(self.__class__)
+ if doc:
+ sys.stdout.write(doc + '\n')
+ sys.stdout.flush()
+
+ def do_shell(self, argv):
+ """\
+ shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and
+ '!' are synonyms.
+ """
+ import subprocess
+ argv = argv[1:]
+ if not argv:
+ argv = os.environ[self.shell_variable]
+ try:
+ p = subprocess.Popen(argv)
+ except EnvironmentError, e:
+ sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror))
+ else:
+ p.wait()
+
+ def do_version(self, argv):
+ """\
+ version Prints SCons version information.
+ """
+ sys.stdout.write(self.parser.version + '\n')
+
+def interact(fs, parser, options, targets, target_top):
+ c = SConsInteractiveCmd(prompt = 'scons>>> ',
+ fs = fs,
+ parser = parser,
+ options = options,
+ targets = targets,
+ target_top = target_top)
+ c.cmdloop()
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index 97e0b190..bcbd0a14 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -68,6 +68,18 @@ import SCons.Taskmaster
import SCons.Util
import SCons.Warnings
+import SCons.Script.Interactive
+
+def fetch_win32_parallel_msg():
+ # A subsidiary function that exists solely to isolate this import
+ # so we don't have to pull it in on all platforms, and so that an
+ # in-line "import" statement in the _main() function below doesn't
+ # cause warnings about local names shadowing use of the 'SCons'
+ # globl in nest scopes and UnboundLocalErrors and the like in some
+ # versions (2.1) of Python.
+ import SCons.Platform.win32
+ SCons.Platform.win32.parallel_msg
+
#
class SConsPrintHelpException(Exception):
@@ -730,7 +742,6 @@ def version_string(label, module):
module.__buildsys__)
def _main(parser):
- import SCons
global exit_status
options = parser.values
@@ -750,7 +761,8 @@ def _main(parser):
SCons.Warnings.NoMetaclassSupportWarning,
SCons.Warnings.NoObjectCountWarning,
SCons.Warnings.NoParallelSupportWarning,
- SCons.Warnings.MisleadingKeywordsWarning, ]
+ SCons.Warnings.MisleadingKeywordsWarning,
+ SCons.Warnings.StackSizeWarning, ]
for warning in default_warnings:
SCons.Warnings.enableWarningClass(warning)
SCons.Warnings._warningOut = _scons_internal_warning
@@ -835,10 +847,10 @@ def _main(parser):
SCons.Node.implicit_cache = options.implicit_cache
SCons.Node.implicit_deps_changed = options.implicit_deps_changed
SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
if options.no_exec:
SCons.SConf.dryrun = 1
SCons.Action.execute_actions = None
- CleanTask.execute = CleanTask.show
if options.question:
SCons.SConf.dryrun = 1
if options.clean:
@@ -850,19 +862,6 @@ def _main(parser):
if options.no_progress or options.silent:
progress_display.set_mode(0)
- if options.silent:
- display.set_mode(0)
- if options.silent:
- SCons.Action.print_actions = None
-
- if options.cache_disable:
- SCons.CacheDir.CacheDir = SCons.Util.Null()
- if options.cache_debug:
- SCons.CacheDir.cache_debug = options.cache_debug
- if options.cache_force:
- SCons.CacheDir.cache_force = True
- if options.cache_show:
- SCons.CacheDir.cache_show = True
if options.site_dir:
_load_site_scons_dir(d, options.site_dir)
@@ -887,7 +886,18 @@ def _main(parser):
SCons.Script._Add_Targets(targets + parser.rargs)
SCons.Script._Add_Arguments(xmit_args)
- sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+ # If stdout is not a tty, replace it with a wrapper object to call flush
+ # after every write.
+ #
+ # Tty devices automatically flush after every newline, so the replacement
+ # isn't necessary. Furthermore, if we replace sys.stdout, the readline
+ # module will no longer work. This affects the behavior during
+ # --interactive mode. --interactive should only be used when stdin and
+ # stdout refer to a tty.
+ if not sys.stdout.isatty():
+ sys.stdout = SCons.Util.Unbuffered(sys.stdout)
+ if not sys.stderr.isatty():
+ sys.stderr = SCons.Util.Unbuffered(sys.stderr)
memory_stats.append('before reading SConscript files:')
count_stats.append(('pre-', 'read'))
@@ -956,6 +966,47 @@ def _main(parser):
SCons.Node.implicit_cache = options.implicit_cache
SCons.Node.FS.set_duplicate(options.duplicate)
fs.set_max_drift(options.max_drift)
+ if not options.stack_size is None:
+ SCons.Job.stack_size = options.stack_size
+
+ platform = SCons.Platform.platform_module()
+
+ if options.interactive:
+ SCons.Script.Interactive.interact(fs, OptionsParser, options,
+ targets, target_top)
+
+ else:
+
+ # Build the targets
+ nodes = _build_targets(fs, options, targets, target_top)
+ if not nodes:
+ exit_status = 2
+
+def _build_targets(fs, options, targets, target_top):
+
+ progress_display.set_mode(not (options.no_progress or options.silent))
+ display.set_mode(not options.silent)
+ SCons.Action.print_actions = not options.silent
+ SCons.Action.execute_actions = not options.no_exec
+ SCons.SConf.dryrun = options.no_exec
+
+ if options.diskcheck:
+ SCons.Node.FS.set_diskcheck(options.diskcheck)
+
+ _set_debug_values(options)
+ SCons.Node.implicit_cache = options.implicit_cache
+ SCons.Node.implicit_deps_changed = options.implicit_deps_changed
+ SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged
+
+ SCons.CacheDir.cache_enabled = not options.cache_disable
+ SCons.CacheDir.cache_debug = options.cache_debug
+ SCons.CacheDir.cache_force = options.cache_force
+ SCons.CacheDir.cache_show = options.cache_show
+
+ if options.no_exec:
+ CleanTask.execute = CleanTask.show
+ else:
+ CleanTask.execute = CleanTask.remove
lookup_top = None
if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default:
@@ -1003,7 +1054,7 @@ def _main(parser):
if not targets:
sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
- sys.exit(2)
+ return None
def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs):
if isinstance(x, SCons.Node.Node):
@@ -1046,7 +1097,7 @@ def _main(parser):
opening_message = "Cleaning targets ..."
closing_message = "done cleaning targets."
if options.keep_going:
- closing_message = "done cleaning targets (errors occurred during clean)."
+ failure_message = "done cleaning targets (errors occurred during clean)."
else:
failure_message = "cleaning terminated because of errors."
except AttributeError:
@@ -1091,8 +1142,7 @@ def _main(parser):
msg = "parallel builds are unsupported by this version of Python;\n" + \
"\tignoring -j or num_jobs option.\n"
elif sys.platform == 'win32':
- import SCons.Platform.win32
- msg = SCons.Platform.win32.parallel_msg
+ msg = fetch_win32_parallel_msg()
if msg:
SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
@@ -1101,7 +1151,15 @@ def _main(parser):
try:
progress_display("scons: " + opening_message)
- jobs.run()
+ try:
+ jobs.run()
+ except KeyboardInterrupt:
+ # If we are in interactive mode, a KeyboardInterrupt
+ # interrupts only this current run. Return 'nodes' normally
+ # so that the outer loop can clean up the nodes and continue.
+ if options.interactive:
+ print "Build interrupted."
+ # Continue and return normally
finally:
jobs.cleanup()
if exit_status:
@@ -1114,6 +1172,8 @@ def _main(parser):
memory_stats.append('after building targets:')
count_stats.append(('post-', 'build'))
+ return nodes
+
def _exec_main(parser, values):
sconsflags = os.environ.get('SCONSFLAGS', '')
all_args = string.split(sconsflags) + sys.argv[1:]
diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py
index 46ece273..8f7116de 100644
--- a/src/engine/SCons/Script/SConsOptions.py
+++ b/src/engine/SCons/Script/SConsOptions.py
@@ -122,6 +122,7 @@ class SConsValues(optparse.Values):
'no_exec',
'num_jobs',
'random',
+ 'stack_size',
]
def set_option(self, name, value):
@@ -163,6 +164,11 @@ class SConsValues(optparse.Values):
# Set this right away so it can affect the rest of the
# file/Node lookups while processing the SConscript files.
SCons.Node.FS.set_diskcheck(value)
+ elif name == 'stack_size':
+ try:
+ value = int(value)
+ except ValueError:
+ raise SCons.Errors.UserError, "An integer is required: %s"%repr(value)
self.__SConscript_settings__[name] = value
@@ -466,6 +472,7 @@ def Parser(version):
usage="usage: scons [OPTION] [TARGET] ...",)
op.preserve_unknown_options = True
+ op.version = version
# Add the options to the parser we just created.
#
@@ -667,6 +674,11 @@ def Parser(version):
action="callback", callback=opt_implicit_deps,
help="Ignore changes in implicit dependencies.")
+ op.add_option('--interact', '--interactive',
+ dest='interactive', default=False,
+ action="store_true",
+ help="Run in interactive mode.")
+
op.add_option('-j', '--jobs',
nargs=1, type="int",
dest="num_jobs", default=1,
@@ -730,6 +742,13 @@ def Parser(version):
help="Use DIR instead of the usual site_scons dir.",
metavar="DIR")
+ op.add_option('--stack-size',
+ nargs=1, type="int",
+ dest='stack_size',
+ action="store",
+ help="Set the stack size of the threads used to run jobs to N kilobytes.",
+ metavar="N")
+
op.add_option('--taskmastertrace',
nargs=1,
dest="taskmastertrace_file", default=None,
@@ -777,8 +796,8 @@ def Parser(version):
help="Search up directory tree for SConstruct, "
"build Default() targets from local SConscript.")
- def opt_version(option, opt, value, parser, version=version):
- sys.stdout.write(version + '\n')
+ def opt_version(option, opt, value, parser):
+ sys.stdout.write(parser.version + '\n')
sys.exit(0)
op.add_option("-v", "--version",
action="callback", callback=opt_version,
diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py
index 989f1dd5..7a565baa 100644
--- a/src/engine/SCons/Subst.py
+++ b/src/engine/SCons/Subst.py
@@ -39,11 +39,11 @@ import UserString
import SCons.Errors
-from SCons.Util import is_String, is_List, is_Tuple
+from SCons.Util import is_String, is_Sequence
# Indexed by the SUBST_* constants below.
-_strconv = [SCons.Util.to_String,
- SCons.Util.to_String,
+_strconv = [SCons.Util.to_String_for_subst,
+ SCons.Util.to_String_for_subst,
SCons.Util.to_String_for_signature]
@@ -188,7 +188,7 @@ class NLWrapper:
list = self.list
if list is None:
list = []
- elif not is_List(list) and not is_Tuple(list):
+ elif not is_Sequence(list):
list = [list]
# The map(self.func) call is what actually turns
# a list into appropriate proxies.
@@ -203,10 +203,10 @@ class Targets_or_Sources(UserList.UserList):
wrapping a NLWrapper. This class handles the different methods used
to access the list, calling the NLWrapper to create proxies on demand.
- Note that we subclass UserList.UserList purely so that the is_List()
- function will identify an object of this class as a list during
- variable expansion. We're not really using any UserList.UserList
- methods in practice.
+ Note that we subclass UserList.UserList purely so that the
+ is_Sequence() function will identify an object of this class as
+ a list during variable expansion. We're not really using any
+ UserList.UserList methods in practice.
"""
def __init__(self, nl):
self.nl = nl
@@ -312,6 +312,25 @@ _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
# Indexed by the SUBST_* constants above.
_regex_remove = [ _rm, None, _remove ]
+def _rm_list(list):
+ #return [ l for l in list if not l in ('$(', '$)') ]
+ return filter(lambda l: not l in ('$(', '$)'), list)
+
+def _remove_list(list):
+ result = []
+ do_append = result.append
+ for l in list:
+ if l == '$(':
+ do_append = lambda x: None
+ elif l == '$)':
+ do_append = result.append
+ else:
+ do_append(l)
+ return result
+
+# Indexed by the SUBST_* constants above.
+_list_remove = [ _rm_list, None, _remove_list ]
+
# Regular expressions for splitting strings and handling substitutions,
# for use by the scons_subst() and scons_subst_list() functions:
#
@@ -342,7 +361,8 @@ _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
- """Expand a string containing construction variable substitutions.
+ """Expand a string or list containing construction variable
+ substitutions.
This is the work-horse function for substitutions in file names
and the like. The companion scons_subst_list() function (below)
@@ -427,11 +447,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
var = string.split(key, '.')[0]
lv[var] = ''
return self.substitute(s, lv)
- elif is_List(s) or is_Tuple(s):
+ elif is_Sequence(s):
def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
return conv(substitute(l, lvars))
- r = map(func, s)
- return string.join(r)
+ return map(func, s)
elif callable(s):
try:
s = s(target=self.target,
@@ -458,6 +477,7 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
separate tokens.
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
+ args = str(args) # In case it's a UserString.
try:
def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
return conv(expand(match.group(1), lvars))
@@ -472,11 +492,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
result = []
for a in args:
result.append(self.conv(self.expand(a, lvars)))
- try:
- result = string.join(result, '')
- except TypeError:
- if len(result) == 1:
- result = result[0]
+ if len(result) == 1:
+ result = result[0]
+ else:
+ result = string.join(map(str, result), '')
return result
else:
return self.expand(args, lvars)
@@ -524,6 +543,10 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
# Compress strings of white space characters into
# a single space.
result = string.strip(_space_sep.sub(' ', result))
+ elif is_Sequence(result):
+ remove = _list_remove[mode]
+ if remove:
+ result = remove(result)
return result
@@ -634,7 +657,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
lv[var] = ''
self.substitute(s, lv, 0)
self.this_word()
- elif is_List(s) or is_Tuple(s):
+ elif is_Sequence(s):
for a in s:
self.substitute(a, lvars, 1)
self.next_word()
@@ -666,6 +689,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
+ args = str(args) # In case it's a UserString.
args = _separate_args.findall(args)
for a in args:
if a[0] in ' \t\n\r\f\v':
@@ -827,18 +851,18 @@ def scons_subst_once(strSubst, env, key):
a = match.group(1)
if a in matchlist:
a = val
- if is_List(a) or is_Tuple(a):
+ if is_Sequence(a):
return string.join(map(str, a))
else:
return str(a)
- if is_List(strSubst) or is_Tuple(strSubst):
+ if is_Sequence(strSubst):
result = []
for arg in strSubst:
if is_String(arg):
if arg in matchlist:
arg = val
- if is_List(arg) or is_Tuple(arg):
+ if is_Sequence(arg):
result.extend(arg)
else:
result.append(arg)
diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py
index b6e5b71d..c0641641 100644
--- a/src/engine/SCons/SubstTests.py
+++ b/src/engine/SCons/SubstTests.py
@@ -190,6 +190,7 @@ class SubstTestCase(unittest.TestCase):
'T' : ('x', 'y'),
'CS' : cs,
'CL' : cl,
+ 'US' : UserString.UserString('us'),
# Test function calls within ${}.
'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
@@ -317,6 +318,12 @@ class SubstTestCase(unittest.TestCase):
'$CS', 'cs',
'$CL', 'cl',
+ # Various uses of UserString.
+ UserString.UserString('x'), 'x',
+ UserString.UserString('$X'), 'x',
+ UserString.UserString('$US'), 'us',
+ '$US', 'us',
+
# Test function calls within ${}.
'$FUNCCALL', 'a xc b',
@@ -404,9 +411,9 @@ class SubstTestCase(unittest.TestCase):
"This is test",
["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
- "| $( a | b $) | c 1",
- "| a | b | c 1",
- "| | c 1",
+ ["|", "$(", "a", "|", "b", "$)", "|", "c", "1"],
+ ["|", "a", "|", "b", "|", "c", "1"],
+ ["|", "|", "c", "1"],
]
gvars = env.Dictionary()
@@ -570,7 +577,7 @@ class SubstTestCase(unittest.TestCase):
cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
- assert newcmd == 'test foo bar call test', newcmd
+ assert newcmd == ['test', 'foo', 'bar', 'call', 'test'], newcmd
cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
assert len(cmd_list) == 1, cmd_list
@@ -653,6 +660,7 @@ class SubstTestCase(unittest.TestCase):
'L' : ['x', 'y'],
'CS' : cs,
'CL' : cl,
+ 'US' : UserString.UserString('us'),
# Test function calls within ${}.
'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}',
@@ -786,6 +794,16 @@ class SubstTestCase(unittest.TestCase):
'$CL', [['cl']],
['$CL'], [['cl']],
+ # Various uses of UserString.
+ UserString.UserString('x'), [['x']],
+ [UserString.UserString('x')], [['x']],
+ UserString.UserString('$X'), [['x']],
+ [UserString.UserString('$X')], [['x']],
+ UserString.UserString('$US'), [['us']],
+ [UserString.UserString('$US')], [['us']],
+ '$US', [['us']],
+ ['$US'], [['us']],
+
# Test function calls within ${}.
'$FUNCCALL', [['a', 'xc', 'b']],
diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py
index 3bb4225d..9db8138c 100644
--- a/src/engine/SCons/Taskmaster.py
+++ b/src/engine/SCons/Taskmaster.py
@@ -562,21 +562,6 @@ class Taskmaster:
childstate = map(lambda N: (N, N.get_state()), children)
- # Skip this node if any of its children have failed. This
- # catches the case where we're descending a top-level target
- # and one of our children failed while trying to be built
- # by a *previous* descent of an earlier top-level target.
- failed_children = filter(lambda I: I[1] == SCons.Node.failed,
- childstate)
- if failed_children:
- node.set_state(SCons.Node.failed)
- if S: S.child_failed = S.child_failed + 1
- if T:
- c = map(str, failed_children)
- c.sort()
- T.write(' children failed:\n %s\n' % c)
- continue
-
# Detect dependency cycles:
pending_nodes = filter(lambda I: I[1] == SCons.Node.pending, childstate)
if pending_nodes:
@@ -632,6 +617,35 @@ class Taskmaster:
T.write(' waiting on side effects:\n %s\n' % c)
continue
+ # Skip this node if any of its children have failed.
+ #
+ # This catches the case where we're descending a top-level
+ # target and one of our children failed while trying to be
+ # built by a *previous* descent of an earlier top-level
+ # target.
+ #
+ # It can also occur if a node is reused in multiple
+ # targets. One first descends though the one of the
+ # target, the next time occurs through the other target.
+ #
+ # Note that we can only have failed_children if the
+ # --keep-going flag was used, because without it the build
+ # will stop before diving in the other branch.
+ #
+ # Note that even if one of the children fails, we still
+ # added the other children to the list of candidate nodes
+ # to keep on building (--keep-going).
+ failed_children = filter(lambda I: I[1] == SCons.Node.failed,
+ childstate)
+ if failed_children:
+ node.set_state(SCons.Node.failed)
+ if S: S.child_failed = S.child_failed + 1
+ if T:
+ c = map(lambda I: str(I[0]), failed_children)
+ c.sort()
+ T.write(' children failed:\n %s\n' % c)
+ continue
+
# The default when we've gotten through all of the checks above:
# this node is ready to be built.
if S: S.build = S.build + 1
diff --git a/src/engine/SCons/Tool/applelink.py b/src/engine/SCons/Tool/applelink.py
index a65a4aff..532301f6 100644
--- a/src/engine/SCons/Tool/applelink.py
+++ b/src/engine/SCons/Tool/applelink.py
@@ -35,12 +35,14 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Util
-import gnulink
+# Even though the Mac is based on the GNU toolchain, it doesn't understand
+# the -rpath option, so we use the "link" tool instead of "gnulink".
+import link
def generate(env):
"""Add Builders and construction variables for applelink to an
Environment."""
- gnulink.generate(env)
+ link.generate(env)
env['FRAMEWORKPATHPREFIX'] = '-F'
env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__)}'
diff --git a/src/engine/SCons/Tool/gfortran.py b/src/engine/SCons/Tool/gfortran.py
new file mode 100644
index 00000000..f3db6930
--- /dev/null
+++ b/src/engine/SCons/Tool/gfortran.py
@@ -0,0 +1,62 @@
+"""SCons.Tool.gfortran
+
+Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran
+2003 compiler.
+
+There normally shouldn't be any need to import this module directly.
+It will usually be imported through the generic SCons.Tool.Tool()
+selection method.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import SCons.Util
+
+import fortran
+
+def generate(env):
+ """Add Builders and construction variables for gfortran to an
+ Environment."""
+ fortran.generate(env)
+
+ # which one is the good one ? ifort uses _FORTRAND, ifl FORTRAN,
+ # aixf77 F77 ...
+ #env['_FORTRAND'] = 'gfortran'
+ env['FORTRAN'] = 'gfortran'
+
+ # XXX does this need to be set too ?
+ #env['SHFORTRAN'] = 'gfortran'
+
+ if env['PLATFORM'] in ['cygwin', 'win32']:
+ env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS')
+ else:
+ env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -fPIC')
+
+ # XXX; Link problems: we need to add -lgfortran somewhere...
+
+def exists(env):
+ return env.Detect('gfortran')
diff --git a/src/engine/SCons/Tool/gfortran.xml b/src/engine/SCons/Tool/gfortran.xml
new file mode 100644
index 00000000..ba0fe76a
--- /dev/null
+++ b/src/engine/SCons/Tool/gfortran.xml
@@ -0,0 +1,15 @@
+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+<tool name="gfortran">
+<summary>
+Sets construction variables for the GNU F95/F2003 GNU compiler.
+</summary>
+<sets>
+FORTRAN
+SHFORTRANFLAGS
+</sets>
+</tool>
diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py
index 673c8486..02cc52af 100644
--- a/src/engine/SCons/Tool/intelc.py
+++ b/src/engine/SCons/Tool/intelc.py
@@ -41,11 +41,14 @@ is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or
(os.environ.has_key('PROCESSOR_ARCHITEW6432') and
os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64'))
is_linux = sys.platform == 'linux2'
+is_mac = sys.platform == 'darwin'
if is_windows:
import SCons.Tool.msvc
elif is_linux:
import SCons.Tool.gcc
+elif is_mac:
+ import SCons.Tool.gcc
import SCons.Util
import SCons.Warnings
@@ -106,6 +109,11 @@ def check_abi(abi):
'x86_64' : 'x86_64',
'em64t' : 'x86_64',
'amd64' : 'x86_64'}
+ if is_mac:
+ valid_abis = {'ia32' : 'ia32',
+ 'x86' : 'ia32',
+ 'x86_64' : 'x86_64',
+ 'em64t' : 'x86_64'}
try:
abi = valid_abis[abi]
except KeyError:
@@ -196,8 +204,22 @@ def get_all_compiler_versions():
if ok:
versions.append(subkey)
else:
- # Registry points to nonexistent dir. Ignore this version.
- print "Ignoring "+str(get_intel_registry_value('ProductDir', subkey, 'IA32'))
+ try:
+ # Registry points to nonexistent dir. Ignore this
+ # version.
+ value = get_intel_registry_value('ProductDir', subkey, 'IA32')
+ except MissingRegistryError, e:
+
+ # Registry key is left dangling (potentially
+ # after uninstalling).
+
+ print \
+ "scons: *** Ignoring the registry key for the Intel compiler version %s.\n" \
+ "scons: *** It seems that the compiler was uninstalled and that the registry\n" \
+ "scons: *** was not cleaned up properly.\n" % subkey
+ else:
+ print "scons: *** Ignoring "+str(value)
+
i = i + 1
except EnvironmentError:
# no more subkeys
@@ -205,11 +227,22 @@ def get_all_compiler_versions():
elif is_linux:
for d in glob.glob('/opt/intel_cc_*'):
# Typical dir here is /opt/intel_cc_80.
- versions.append(re.search(r'cc_(.*)$', d).group(1))
+ m = re.search(r'cc_(.*)$', d)
+ if m:
+ versions.append(m.group(1))
+ for d in glob.glob('/opt/intel/cc*/*'):
+ # Typical dir here is /opt/intel/cc/9.0 for IA32,
+ # /opt/intel/cce/9.0 for EMT64 (AMD64)
+ m = re.search(r'([0-9.]+)$', d)
+ if m:
+ versions.append(m.group(1))
+ elif is_mac:
for d in glob.glob('/opt/intel/cc*/*'):
# Typical dir here is /opt/intel/cc/9.0 for IA32,
# /opt/intel/cce/9.0 for EMT64 (AMD64)
- versions.append(re.search(r'([0-9.]+)$', d).group(1))
+ m = re.search(r'([0-9.]+)$', d)
+ if m:
+ versions.append(m.group(1))
versions = uniquify(versions) # remove dups
versions.sort(vercmp)
return versions
@@ -229,7 +262,7 @@ def get_intel_compiler_top(version, abi):
if not os.path.exists(os.path.join(top, "Bin", "icl.exe")):
raise MissingDirError, \
"Can't find Intel compiler in %s"%(top)
- elif is_linux:
+ elif is_mac or is_linux:
# first dir is new (>=9.0) style, second is old (8.0) style.
dirs=('/opt/intel/cc/%s', '/opt/intel_cc_%s')
if abi == 'x86_64':
@@ -256,7 +289,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
If topdir is used, version and abi are ignored.
verbose: (int) if >0, prints compiler version used.
"""
- if not (is_linux or is_windows):
+ if not (is_mac or is_linux or is_windows):
# can't handle this platform
return
@@ -264,6 +297,8 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
SCons.Tool.msvc.generate(env)
elif is_linux:
SCons.Tool.gcc.generate(env)
+ elif is_mac:
+ SCons.Tool.gcc.generate(env)
# if version is unspecified, use latest
vlist = get_all_compiler_versions()
@@ -284,7 +319,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
# alternatives are ia64 for Itanium, or amd64 or em64t or x86_64 (all synonyms here)
abi = check_abi(abi)
if abi is None:
- if is_linux:
+ if is_mac or is_linux:
# Check if we are on 64-bit linux, default to 64 then.
uname_m = os.uname()[4]
if uname_m == 'x86_64':
@@ -308,7 +343,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
# on $PATH and the user is importing their env.
class ICLTopDirWarning(SCons.Warnings.Warning):
pass
- if is_linux and not env.Detect('icc') or \
+ if (is_mac or is_linux) and not env.Detect('icc') or \
is_windows and not env.Detect('icl'):
SCons.Warnings.enableWarningClass(ICLTopDirWarning)
@@ -325,11 +360,14 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
if topdir:
if verbose:
- print "Intel C compiler: using version '%s' (%g), abi %s, in '%s'"%\
- (version, linux_ver_normalize(version),abi,topdir)
+ print "Intel C compiler: using version %s (%g), abi %s, in '%s'"%\
+ (repr(version), linux_ver_normalize(version),abi,topdir)
if is_linux:
# Show the actual compiler version by running the compiler.
os.system('%s/bin/icc --version'%topdir)
+ if is_mac:
+ # Show the actual compiler version by running the compiler.
+ os.system('%s/bin/icc --version'%topdir)
env['INTEL_C_COMPILER_TOP'] = topdir
if is_linux:
@@ -339,11 +377,22 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
'LD_LIBRARY_PATH' : 'lib'}
for p in paths:
env.PrependENVPath(p, os.path.join(topdir, paths[p]))
+ if is_mac:
+ paths={'INCLUDE' : 'include',
+ 'LIB' : 'lib',
+ 'PATH' : 'bin',
+ 'LD_LIBRARY_PATH' : 'lib'}
+ for p in paths:
+ env.PrependENVPath(p, os.path.join(topdir, paths[p]))
if is_windows:
# env key reg valname default subdir of top
paths=(('INCLUDE', 'IncludeDir', 'Include'),
('LIB' , 'LibDir', 'Lib'),
('PATH' , 'BinDir', 'Bin'))
+ # We are supposed to ignore version if topdir is set, so set
+ # it to the emptry string if it's not already set.
+ if version is None:
+ version = ''
# Each path has a registry entry, use that or default to subdir
for p in paths:
try:
@@ -392,7 +441,9 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
licdir = None
for ld in [envlicdir, reglicdir]:
- if ld and os.path.exists(ld):
+ # If the string contains an '@', then assume it's a network
+ # license (port@system) and good by definition.
+ if ld and (string.find(ld, '@') != -1 or os.path.exists(ld)):
licdir = ld
break
if not licdir:
@@ -409,7 +460,7 @@ def generate(env, version=None, abi=None, topdir=None, verbose=0):
env['ENV']['INTEL_LICENSE_FILE'] = licdir
def exists(env):
- if not (is_linux or is_windows):
+ if not (is_mac or is_linux or is_windows):
# can't handle this platform
return 0
@@ -424,6 +475,8 @@ def exists(env):
return env.Detect('icl')
elif is_linux:
return env.Detect('icc')
+ elif is_mac:
+ return env.Detect('icc')
return detected
# end of file
diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py
index 4f221c04..6594ecc5 100644
--- a/src/engine/SCons/Tool/jar.py
+++ b/src/engine/SCons/Tool/jar.py
@@ -38,19 +38,32 @@ import SCons.Util
def jarSources(target, source, env, for_signature):
"""Only include sources that are not a manifest file."""
- jarchdir = env.subst('$JARCHDIR', target=target, source=source)
- if jarchdir:
- jarchdir = env.fs.Dir(jarchdir)
+ try:
+ env['JARCHDIR']
+ except KeyError:
+ jarchdir_set = False
+ else:
+ jarchdir_set = True
+ jarchdir = env.subst('$JARCHDIR', target=target, source=source)
+ if jarchdir:
+ jarchdir = env.fs.Dir(jarchdir)
result = []
for src in source:
contents = src.get_contents()
if contents[:16] != "Manifest-Version":
- if jarchdir:
+ if jarchdir_set:
+ _chdir = jarchdir
+ else:
+ try:
+ _chdir = src.attributes.java_classdir
+ except AttributeError:
+ _chdir = None
+ if _chdir:
# If we are changing the dir with -C, then sources should
# be relative to that directory.
- src = SCons.Subst.Literal(src.get_path(jarchdir))
+ src = SCons.Subst.Literal(src.get_path(_chdir))
result.append('-C')
- result.append(jarchdir)
+ result.append(_chdir)
result.append(src)
return result
diff --git a/src/engine/SCons/Tool/jar.xml b/src/engine/SCons/Tool/jar.xml
index a0d730e8..9e8fefac 100644
--- a/src/engine/SCons/Tool/jar.xml
+++ b/src/engine/SCons/Tool/jar.xml
@@ -34,6 +34,11 @@ If the &cv-link-JARCHDIR; value is set, the
command will change to the specified directory using the
<option>-C</option>
option.
+If &cv-JARCHDIR; is not set explicitly,
+&SCons; will use the top of any subdirectory tree
+in which Java <filename>.class</filename>
+were built by the &b-link-Java; Builder.
+
If the contents any of the source files begin with the string
<literal>Manifest-Version</literal>,
the file is assumed to be a manifest
diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py
index be1a81aa..b60aa875 100644
--- a/src/engine/SCons/Tool/link.py
+++ b/src/engine/SCons/Tool/link.py
@@ -44,6 +44,11 @@ def smart_link(source, target, env, for_signature):
return '$CXX'
return '$CC'
+def shlib_emitter(target, source, env):
+ for tgt in target:
+ tgt.attributes.shared = 1
+ return (target, source)
+
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
SCons.Tool.createSharedLibBuilder(env)
@@ -54,14 +59,14 @@ def generate(env):
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
# don't set up the emitter, cause AppendUnique will generate a list
# starting with None :-(
- #env['SHLIBEMITTER']= None
+ env.Append(SHLIBEMITTER = [shlib_emitter])
env['SMARTLINK'] = smart_link
env['LINK'] = "$SMARTLINK"
env['LINKFLAGS'] = SCons.Util.CLVar('')
env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LIBDIRPREFIX']='-L'
env['LIBDIRSUFFIX']=''
- env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIX, LIBSUFFIX, __env__)}'
+ env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
env['LIBLINKPREFIX']='-l'
env['LIBLINKSUFFIX']=''
diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py
index 25f35646..42eabafb 100644
--- a/src/engine/SCons/Tool/mslink.py
+++ b/src/engine/SCons/Tool/mslink.py
@@ -76,6 +76,9 @@ def windowsShlinkSources(target, source, env, for_signature):
def windowsLibEmitter(target, source, env):
SCons.Tool.msvc.validate_vars(env)
+ extratargets = []
+ extrasources = []
+
dll = env.FindIxes(target, "SHLIBPREFIX", "SHLIBSUFFIX")
no_import_lib = env.get('no_import_lib', 0)
@@ -87,38 +90,44 @@ def windowsLibEmitter(target, source, env):
not env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"):
# append a def file to the list of sources
- source.append(env.ReplaceIxes(dll,
- "SHLIBPREFIX", "SHLIBSUFFIX",
- "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))
+ extrasources.append(
+ env.ReplaceIxes(dll,
+ "SHLIBPREFIX", "SHLIBSUFFIX",
+ "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))
version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
# MSVC 8 automatically generates .manifest files that must be installed
- target.append(env.ReplaceIxes(dll,
- "SHLIBPREFIX", "SHLIBSUFFIX",
- "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX"))
+ extratargets.append(
+ env.ReplaceIxes(dll,
+ "SHLIBPREFIX", "SHLIBSUFFIX",
+ "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX"))
if env.has_key('PDB') and env['PDB']:
pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
- target.append(pdb)
+ extratargets.append(pdb)
target[0].attributes.pdb = pdb
if not no_import_lib and \
not env.FindIxes(target, "LIBPREFIX", "LIBSUFFIX"):
# Append an import library to the list of targets.
- target.append(env.ReplaceIxes(dll,
- "SHLIBPREFIX", "SHLIBSUFFIX",
- "LIBPREFIX", "LIBSUFFIX"))
+ extratargets.append(
+ env.ReplaceIxes(dll,
+ "SHLIBPREFIX", "SHLIBSUFFIX",
+ "LIBPREFIX", "LIBSUFFIX"))
# and .exp file is created if there are exports from a DLL
- target.append(env.ReplaceIxes(dll,
- "SHLIBPREFIX", "SHLIBSUFFIX",
- "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX"))
+ extratargets.append(
+ env.ReplaceIxes(dll,
+ "SHLIBPREFIX", "SHLIBSUFFIX",
+ "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX"))
- return (target, source)
+ return (target+extratargets, source+extrasources)
def prog_emitter(target, source, env):
SCons.Tool.msvc.validate_vars(env)
+ extratargets = []
+
exe = env.FindIxes(target, "PROGPREFIX", "PROGSUFFIX")
if not exe:
raise SCons.Errors.UserError, "An executable should have exactly one target with the suffix: %s" % env.subst("$PROGSUFFIX")
@@ -126,16 +135,17 @@ def prog_emitter(target, source, env):
version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
# MSVC 8 automatically generates .manifest files that have to be installed
- target.append(env.ReplaceIxes(exe,
- "PROGPREFIX", "PROGSUFFIX",
- "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX"))
+ extratargets.append(
+ env.ReplaceIxes(exe,
+ "PROGPREFIX", "PROGSUFFIX",
+ "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX"))
if env.has_key('PDB') and env['PDB']:
pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
- target.append(pdb)
+ extratargets.append(pdb)
target[0].attributes.pdb = pdb
- return (target,source)
+ return (target+extratargets,source)
def RegServerFunc(target, source, env):
if env.has_key('register') and env['register']:
diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py
index 105f42e5..d67cddb7 100644
--- a/src/engine/SCons/Tool/qt.py
+++ b/src/engine/SCons/Tool/qt.py
@@ -66,7 +66,7 @@ def checkMocIncluded(target, source, env):
cpp = source[0]
# looks like cpp.includes is cleared before the build stage :-(
# not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/
- path = SCons.Defaults.CScan.path_function(env, moc.cwd)
+ path = SCons.Defaults.CScan.path(env, moc.cwd)
includes = SCons.Defaults.CScan(cpp, env, path)
if not moc in includes:
SCons.Warnings.warn(
diff --git a/src/engine/SCons/Tool/rmic.py b/src/engine/SCons/Tool/rmic.py
index 4b48e0b9..ed5c8ee7 100644
--- a/src/engine/SCons/Tool/rmic.py
+++ b/src/engine/SCons/Tool/rmic.py
@@ -79,9 +79,13 @@ def emit_rmic_classes(target, source, env):
s.attributes.java_classname = classname
slist.append(s)
+ stub_suffixes = ['_Stub']
+ if env.get('JAVAVERSION') == '1.4':
+ stub_suffixes.append('_Skel')
+
tlist = []
for s in source:
- for suff in ['_Skel', '_Stub']:
+ for suff in stub_suffixes:
fname = string.replace(s.attributes.java_classname, '.', os.sep) + \
suff + class_suffix
t = target[0].File(fname)
diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py
index 8ca1b89c..eba49a77 100644
--- a/src/engine/SCons/Tool/swig.py
+++ b/src/engine/SCons/Tool/swig.py
@@ -50,7 +50,8 @@ def swigSuffixEmitter(env, source):
else:
return '$SWIGCFILESUFFIX'
-_reModule = re.compile(r'%module\s+(.+)')
+# Match '%module test', as well as '%module(directors="1") test'
+_reModule = re.compile(r'%module(?:\s*\(.*\))?\s+(.+)')
def _swigEmitter(target, source, env):
swigflags = env.subst("$SWIGFLAGS", target=target, source=source)
diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py
index bbae25e0..c3156a3a 100644
--- a/src/engine/SCons/Tool/tex.py
+++ b/src/engine/SCons/Tool/tex.py
@@ -42,7 +42,7 @@ import SCons.Node
import SCons.Node.FS
import SCons.Util
-warning_rerun_re = re.compile("^LaTeX Warning:.*Rerun", re.MULTILINE)
+warning_rerun_re = re.compile('(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)', re.MULTILINE)
rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct"
rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)
@@ -76,26 +76,52 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
basename = SCons.Util.splitext(str(source[0]))[0]
basedir = os.path.split(str(source[0]))[0]
-
- # Notice that all the filenames are not prefixed with the basedir.
- # That's because the *COM variables have the cd command in the prolog.
-
- bblfilename = basename + '.bbl'
+ basefile = os.path.split(str(basename))[1]
+ abspath = os.path.abspath(basedir)
+ targetbase = SCons.Util.splitext(str(target[0]))[0]
+ targetdir = os.path.split(str(target[0]))[0]
+
+ # Not sure if these environment changes should go here or make the
+ # user do them I undo all but TEXPICTS but there is still the side
+ # effect of creating the empty (':') entries in the environment.
+
+ def modify_env_var(env, var, abspath):
+ try:
+ save = env['ENV'][var]
+ except KeyError:
+ save = ':'
+ env['ENV'][var] = ''
+ if SCons.Util.is_List(env['ENV'][var]):
+ env['ENV'][var] = [abspath] + env['ENV'][var]
+ else:
+ env['ENV'][var] = abspath + os.pathsep + env['ENV'][var]
+ return save
+
+ texinputs_save = modify_env_var(env, 'TEXINPUTS', abspath)
+ bibinputs_save = modify_env_var(env, 'BIBINPUTS', abspath)
+ bstinputs_save = modify_env_var(env, 'BSTINPUTS', abspath)
+ texpicts_save = modify_env_var(env, 'TEXPICTS', abspath)
+
+ # Create these file names with the target directory since they will
+ # be made there. That's because the *COM variables have the cd
+ # command in the prolog.
+
+ bblfilename = os.path.join(targetdir, basefile + '.bbl')
bblContents = ""
if os.path.exists(bblfilename):
bblContents = open(bblfilename, "rb").read()
- idxfilename = basename + '.idx'
+ idxfilename = os.path.join(targetdir, basefile + '.idx')
idxContents = ""
if os.path.exists(idxfilename):
idxContents = open(idxfilename, "rb").read()
- tocfilename = basename + '.toc'
+ tocfilename = os.path.join(targetdir, basefile + '.toc')
tocContents = ""
if os.path.exists(tocfilename):
tocContents = open(tocfilename, "rb").read()
- # Run LaTeX once to generate a new aux file.
+ # Run LaTeX once to generate a new aux file and log file.
XXXLaTeXAction(target, source, env)
# Decide if various things need to be run, or run again. We check
@@ -104,7 +130,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
# with stubs that don't necessarily generate all of the same files.
# Read the log file to find all .aux files
- logfilename = basename + '.log'
+ logfilename = os.path.join(targetbase + '.log')
auxfiles = []
if os.path.exists(logfilename):
content = open(logfilename, "rb").read()
@@ -112,10 +138,11 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
# Now decide if bibtex will need to be run.
for auxfilename in auxfiles:
- if os.path.exists(os.path.join(basedir, auxfilename)):
- content = open(os.path.join(basedir, auxfilename), "rb").read()
+ target_aux = os.path.join(targetdir, auxfilename)
+ if os.path.exists(target_aux):
+ content = open(target_aux, "rb").read()
if string.find(content, "bibdata") != -1:
- bibfile = env.fs.File(basename)
+ bibfile = env.fs.File(targetbase)
BibTeXAction(bibfile, bibfile, env)
break
@@ -131,7 +158,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
# Now decide if latex will need to be run again due to index.
if os.path.exists(idxfilename) and idxContents != open(idxfilename, "rb").read():
# We must run makeindex
- idxfile = env.fs.File(basename)
+ idxfile = env.fs.File(targetbase)
MakeIndexAction(idxfile, idxfile, env)
must_rerun_latex = 1
@@ -139,7 +166,7 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
XXXLaTeXAction(target, source, env)
# Now decide if latex needs to be run yet again to resolve warnings.
- logfilename = basename + '.log'
+ logfilename = targetbase + '.log'
for _ in range(int(env.subst('$LATEXRETRIES'))):
if not os.path.exists(logfilename):
break
@@ -149,6 +176,15 @@ def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None
not undefined_references_re.search(content):
break
XXXLaTeXAction(target, source, env)
+
+ env['ENV']['TEXINPUTS'] = texinputs_save
+ env['ENV']['BIBINPUTS'] = bibinputs_save
+ env['ENV']['BSTINPUTS'] = bibinputs_save
+
+ # The TEXPICTS enviroment variable is needed by a dvi -> pdf step
+ # later on Mac OSX so leave it,
+ # env['ENV']['TEXPICTS'] = texpicts_save
+
return 0
def LaTeXAuxAction(target = None, source= None, env=None):
@@ -176,27 +212,29 @@ def TeXLaTeXFunction(target = None, source= None, env=None):
def tex_emitter(target, source, env):
base = SCons.Util.splitext(str(source[0]))[0]
- target.append(base + '.aux')
- env.Precious(base + '.aux')
- target.append(base + '.log')
+ targetbase = SCons.Util.splitext(str(target[0]))[0]
+
+ target.append(targetbase + '.aux')
+ env.Precious(targetbase + '.aux')
+ target.append(targetbase + '.log')
for f in source:
content = f.get_contents()
if tableofcontents_re.search(content):
- target.append(base + '.toc')
- env.Precious(base + '.toc')
+ target.append(targetbase + '.toc')
+ env.Precious(targetbase + '.toc')
if makeindex_re.search(content):
- target.append(base + '.ilg')
- target.append(base + '.ind')
- target.append(base + '.idx')
- env.Precious(base + '.idx')
+ target.append(targetbase + '.ilg')
+ target.append(targetbase + '.ind')
+ target.append(targetbase + '.idx')
+ env.Precious(targetbase + '.idx')
if bibliography_re.search(content):
- target.append(base + '.bbl')
- env.Precious(base + '.bbl')
- target.append(base + '.blg')
+ target.append(targetbase + '.bbl')
+ env.Precious(targetbase + '.bbl')
+ target.append(targetbase + '.blg')
- # read log file to get all output file (include .aux files)
- logfilename = base + '.log'
- dir, base_nodir = os.path.split(base)
+ # read log file to get all .aux files
+ logfilename = targetbase + '.log'
+ dir, base_nodir = os.path.split(targetbase)
if os.path.exists(logfilename):
content = open(logfilename, "rb").read()
out_files = openout_re.findall(content)
diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py
index 34f60cb6..0b648e81 100644
--- a/src/engine/SCons/Tool/yacc.py
+++ b/src/engine/SCons/Tool/yacc.py
@@ -54,7 +54,7 @@ def _yaccEmitter(target, source, env, ysuf, hsuf):
# If -d is specified on the command line, yacc will emit a .h
# or .hpp file with the same name as the .c or .cpp output file.
if '-d' in flags:
- target.append(targetBase + env.subst(hsuf))
+ target.append(targetBase + env.subst(hsuf, target=target, source=source))
# If -g is specified on the command line, yacc will emit a .vcg
# file with the same base name as the .y, .yacc, .ym or .yy file.
@@ -108,7 +108,14 @@ def generate(env):
env['YACCFLAGS'] = SCons.Util.CLVar('')
env['YACCCOM'] = '$YACC $YACCFLAGS -o $TARGET $SOURCES'
env['YACCHFILESUFFIX'] = '.h'
- env['YACCHXXFILESUFFIX'] = '.hpp'
+
+ if env['PLATFORM'] == 'darwin':
+ # Bison on Mac OS X just appends ".h" to the generated target .cc
+ # or .cpp file name. Hooray for delayed expansion of variables.
+ env['YACCHXXFILESUFFIX'] = '${TARGET.suffix}.h'
+ else:
+ env['YACCHXXFILESUFFIX'] = '.hpp'
+
env['YACCVCGFILESUFFIX'] = '.vcg'
def exists(env):
diff --git a/src/engine/SCons/Tool/yacc.xml b/src/engine/SCons/Tool/yacc.xml
index 2db06030..aa648b1e 100644
--- a/src/engine/SCons/Tool/yacc.xml
+++ b/src/engine/SCons/Tool/yacc.xml
@@ -87,7 +87,13 @@ file with the specified suffix,
it exists to allow you to specify
what suffix the parser generator will use of its own accord.
The default value is
-<filename>.hpp</filename>.
+<filename>.hpp</filename>,
+except on Mac OS X,
+where the default is
+<filename>${TARGET.suffix}.h</filename>.
+because the default &bison; parser generator just
+appends <filename>.h</filename>
+to the name of the generated C++ file.
</summary>
</cvar>
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 258de0f1..08ce1f2a 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -133,10 +133,17 @@ def to_String_for_signature(obj):
try:
f = obj.for_signature
except AttributeError:
- return to_String(obj)
+ return to_String_for_subst(obj)
else:
return f()
+def to_String_for_subst(s):
+ if is_Sequence( s ):
+ return string.join( map(to_String_for_subst, s) )
+
+ return to_String( s )
+
+
class CallableComposite(UserList):
"""A simple composite callable class that, when called, will invoke all
of its contained callables with the same arguments."""
@@ -344,55 +351,80 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}):
# Yes, all of this manual testing breaks polymorphism, and the real
# Pythonic way to do all of this would be to just try it and handle the
# exception, but handling the exception when it's not the right type is
-# too slow.
-#
-# The actual implementations here have been selected after timings
-# coded up in in bench/is_types.py (from the SCons source tree, see the
-# scons-src distribution). Key results from those timings:
-#
-# -- Storing the type of the object in a variable (t = type(obj))
-# slows down the case where it's a native type and the first
-# comparison will match, but nicely speeds up the case where
-# it's a different native type. Since that's going to be common,
-# it's a good tradeoff.
-#
-# -- The data show that calling isinstance() on an object that's
-# a native type (dict, list or string) is expensive enough that
-# checking up front for whether the object is of type InstanceType
-# is a pretty big win, even though it does slow down the case
-# where it really *is* an object instance a little bit.
-
-def is_Dict(obj):
- t = type(obj)
- return t is DictType or \
- (t is InstanceType and isinstance(obj, UserDict))
-
-def is_List(obj):
- t = type(obj)
- return t is ListType \
- or (t is InstanceType and isinstance(obj, UserList))
-
-def is_Sequence(obj):
- t = type(obj)
- return t is ListType \
- or t is TupleType \
- or (t is InstanceType and isinstance(obj, UserList))
-
-def is_Tuple(obj):
- t = type(obj)
- return t is TupleType
+# often too slow.
-if hasattr(types, 'UnicodeType'):
- def is_String(obj):
+try:
+ class mystr(str):
+ pass
+except TypeError:
+ # An older Python version without new-style classes.
+ #
+ # The actual implementations here have been selected after timings
+ # coded up in in bench/is_types.py (from the SCons source tree,
+ # see the scons-src distribution), mostly against Python 1.5.2.
+ # Key results from those timings:
+ #
+ # -- Storing the type of the object in a variable (t = type(obj))
+ # slows down the case where it's a native type and the first
+ # comparison will match, but nicely speeds up the case where
+ # it's a different native type. Since that's going to be
+ # common, it's a good tradeoff.
+ #
+ # -- The data show that calling isinstance() on an object that's
+ # a native type (dict, list or string) is expensive enough
+ # that checking up front for whether the object is of type
+ # InstanceType is a pretty big win, even though it does slow
+ # down the case where it really *is* an object instance a
+ # little bit.
+ def is_Dict(obj):
+ t = type(obj)
+ return t is DictType or \
+ (t is InstanceType and isinstance(obj, UserDict))
+
+ def is_List(obj):
t = type(obj)
- return t is StringType \
- or t is UnicodeType \
- or (t is InstanceType and isinstance(obj, UserString))
+ return t is ListType \
+ or (t is InstanceType and isinstance(obj, UserList))
+
+ def is_Sequence(obj):
+ t = type(obj)
+ return t is ListType \
+ or t is TupleType \
+ or (t is InstanceType and isinstance(obj, UserList))
+
+ def is_Tuple(obj):
+ t = type(obj)
+ return t is TupleType
+
+ if hasattr(types, 'UnicodeType'):
+ def is_String(obj):
+ t = type(obj)
+ return t is StringType \
+ or t is UnicodeType \
+ or (t is InstanceType and isinstance(obj, UserString))
+ else:
+ def is_String(obj):
+ t = type(obj)
+ return t is StringType \
+ or (t is InstanceType and isinstance(obj, UserString))
else:
+ # A modern Python version with new-style classes, so we can just use
+ # isinstance().
+ def is_Dict(obj):
+ return isinstance(obj, (dict, UserDict))
+
+ def is_List(obj):
+ return isinstance(obj, (list, UserList))
+
+ def is_Sequence(obj):
+ return isinstance(obj, (list, UserList, tuple))
+
+ def is_Tuple(obj):
+ return isinstance(obj, (tuple))
+
def is_String(obj):
- t = type(obj)
- return t is StringType \
- or (t is InstanceType and isinstance(obj, UserString))
+ # Empirically, Python versions with new-style classes all have unicode.
+ return isinstance(obj, (str, unicode, UserString))
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index 3e8085ba..44d6fa89 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -205,6 +205,13 @@ class UtilTestCase(unittest.TestCase):
def test_is_Dict(self):
assert is_Dict({})
assert is_Dict(UserDict())
+ try:
+ class mydict(dict):
+ pass
+ except TypeError:
+ pass
+ else:
+ assert is_Dict(mydict({}))
assert not is_Dict([])
assert not is_Dict(())
assert not is_Dict("")
@@ -215,6 +222,13 @@ class UtilTestCase(unittest.TestCase):
assert is_List([])
import UserList
assert is_List(UserList.UserList())
+ try:
+ class mylist(list):
+ pass
+ except TypeError:
+ pass
+ else:
+ assert is_List(mylist([]))
assert not is_List(())
assert not is_List({})
assert not is_List("")
@@ -231,12 +245,26 @@ class UtilTestCase(unittest.TestCase):
pass
else:
assert is_String(UserString.UserString(''))
+ try:
+ class mystr(str):
+ pass
+ except TypeError:
+ pass
+ else:
+ assert is_String(mystr(''))
assert not is_String({})
assert not is_String([])
assert not is_String(())
def test_is_Tuple(self):
assert is_Tuple(())
+ try:
+ class mytuple(tuple):
+ pass
+ except TypeError:
+ pass
+ else:
+ assert is_Tuple(mytuple(()))
assert not is_Tuple([])
assert not is_Tuple({})
assert not is_Tuple("")
diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py
index b1d39ec6..53549595 100644
--- a/src/engine/SCons/Warnings.py
+++ b/src/engine/SCons/Warnings.py
@@ -73,6 +73,9 @@ class NoParallelSupportWarning(Warning):
class ReservedVariableWarning(Warning):
pass
+class StackSizeWarning(Warning):
+ pass
+
_warningAsException = 0
# The below is a list of 2-tuples. The first element is a class object.
diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py
index 47ae3bea..91e3776b 100644
--- a/src/engine/SCons/compat/__init__.py
+++ b/src/engine/SCons/compat/__init__.py
@@ -158,20 +158,14 @@ import shlex
try:
shlex.split
except AttributeError:
- # Pre-2.3 Python has no shlex.split function.
- def split(s, comments=False):
- import StringIO
- lex = shlex.shlex(StringIO.StringIO(s))
- lex.wordchars = lex.wordchars + '/\\-+,=:'
- result = []
- while True:
- tt = lex.get_token()
- if not tt:
- break
- result.append(tt)
- return result
- shlex.split = split
- del split
+ # Pre-2.3 Python has no shlex.split() function.
+ #
+ # The full white-space splitting semantics of shlex.split() are
+ # complicated to reproduce by hand, so just use a compatibility
+ # version of the shlex module cribbed from Python 2.5 with some
+ # minor modifications for older Python versions.
+ del shlex
+ import_as('_scons_shlex', 'shlex')
try:
import subprocess
diff --git a/src/engine/SCons/compat/_scons_shlex.py b/src/engine/SCons/compat/_scons_shlex.py
new file mode 100644
index 00000000..d6c10357
--- /dev/null
+++ b/src/engine/SCons/compat/_scons_shlex.py
@@ -0,0 +1,319 @@
+# -*- coding: iso-8859-1 -*-
+"""A lexical analyzer class for simple shell-like syntaxes."""
+
+# Module and documentation by Eric S. Raymond, 21 Dec 1998
+# Input stacking and error message cleanup added by ESR, March 2000
+# push_source() and pop_source() made explicit by ESR, January 2001.
+# Posix compliance, split(), string arguments, and
+# iterator interface by Gustavo Niemeyer, April 2003.
+
+import os.path
+import sys
+#from collections import deque
+
+class deque:
+ def __init__(self):
+ self.data = []
+ def __len__(self):
+ return len(self.data)
+ def appendleft(self, item):
+ self.data.insert(0, item)
+ def popleft(self):
+ return self.data.pop(0)
+
+try:
+ basestring
+except NameError:
+ import types
+ def is_basestring(s):
+ return type(s) is types.StringType
+else:
+ def is_basestring(s):
+ return isinstance(s, basestring)
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+__all__ = ["shlex", "split"]
+
+class shlex:
+ "A lexical analyzer class for simple shell-like syntaxes."
+ def __init__(self, instream=None, infile=None, posix=False):
+ if is_basestring(instream):
+ instream = StringIO(instream)
+ if instream is not None:
+ self.instream = instream
+ self.infile = infile
+ else:
+ self.instream = sys.stdin
+ self.infile = None
+ self.posix = posix
+ if posix:
+ self.eof = None
+ else:
+ self.eof = ''
+ self.commenters = '#'
+ self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
+ if self.posix:
+ self.wordchars = self.wordchars + ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+ 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
+ self.whitespace = ' \t\r\n'
+ self.whitespace_split = False
+ self.quotes = '\'"'
+ self.escape = '\\'
+ self.escapedquotes = '"'
+ self.state = ' '
+ self.pushback = deque()
+ self.lineno = 1
+ self.debug = 0
+ self.token = ''
+ self.filestack = deque()
+ self.source = None
+ if self.debug:
+ print 'shlex: reading from %s, line %d' \
+ % (self.instream, self.lineno)
+
+ def push_token(self, tok):
+ "Push a token onto the stack popped by the get_token method"
+ if self.debug >= 1:
+ print "shlex: pushing token " + repr(tok)
+ self.pushback.appendleft(tok)
+
+ def push_source(self, newstream, newfile=None):
+ "Push an input source onto the lexer's input source stack."
+ if is_basestring(newstream):
+ newstream = StringIO(newstream)
+ self.filestack.appendleft((self.infile, self.instream, self.lineno))
+ self.infile = newfile
+ self.instream = newstream
+ self.lineno = 1
+ if self.debug:
+ if newfile is not None:
+ print 'shlex: pushing to file %s' % (self.infile,)
+ else:
+ print 'shlex: pushing to stream %s' % (self.instream,)
+
+ def pop_source(self):
+ "Pop the input source stack."
+ self.instream.close()
+ (self.infile, self.instream, self.lineno) = self.filestack.popleft()
+ if self.debug:
+ print 'shlex: popping to %s, line %d' \
+ % (self.instream, self.lineno)
+ self.state = ' '
+
+ def get_token(self):
+ "Get a token from the input stream (or from stack if it's nonempty)"
+ if self.pushback:
+ tok = self.pushback.popleft()
+ if self.debug >= 1:
+ print "shlex: popping token " + repr(tok)
+ return tok
+ # No pushback. Get a token.
+ raw = self.read_token()
+ # Handle inclusions
+ if self.source is not None:
+ while raw == self.source:
+ spec = self.sourcehook(self.read_token())
+ if spec:
+ (newfile, newstream) = spec
+ self.push_source(newstream, newfile)
+ raw = self.get_token()
+ # Maybe we got EOF instead?
+ while raw == self.eof:
+ if not self.filestack:
+ return self.eof
+ else:
+ self.pop_source()
+ raw = self.get_token()
+ # Neither inclusion nor EOF
+ if self.debug >= 1:
+ if raw != self.eof:
+ print "shlex: token=" + repr(raw)
+ else:
+ print "shlex: token=EOF"
+ return raw
+
+ def read_token(self):
+ quoted = False
+ escapedstate = ' '
+ while True:
+ nextchar = self.instream.read(1)
+ if nextchar == '\n':
+ self.lineno = self.lineno + 1
+ if self.debug >= 3:
+ print "shlex: in state", repr(self.state), \
+ "I see character:", repr(nextchar)
+ if self.state is None:
+ self.token = '' # past end of file
+ break
+ elif self.state == ' ':
+ if not nextchar:
+ self.state = None # end of file
+ break
+ elif nextchar in self.whitespace:
+ if self.debug >= 2:
+ print "shlex: I see whitespace in whitespace state"
+ if self.token or (self.posix and quoted):
+ break # emit current token
+ else:
+ continue
+ elif nextchar in self.commenters:
+ self.instream.readline()
+ self.lineno = self.lineno + 1
+ elif self.posix and nextchar in self.escape:
+ escapedstate = 'a'
+ self.state = nextchar
+ elif nextchar in self.wordchars:
+ self.token = nextchar
+ self.state = 'a'
+ elif nextchar in self.quotes:
+ if not self.posix:
+ self.token = nextchar
+ self.state = nextchar
+ elif self.whitespace_split:
+ self.token = nextchar
+ self.state = 'a'
+ else:
+ self.token = nextchar
+ if self.token or (self.posix and quoted):
+ break # emit current token
+ else:
+ continue
+ elif self.state in self.quotes:
+ quoted = True
+ if not nextchar: # end of file
+ if self.debug >= 2:
+ print "shlex: I see EOF in quotes state"
+ # XXX what error should be raised here?
+ raise ValueError, "No closing quotation"
+ if nextchar == self.state:
+ if not self.posix:
+ self.token = self.token + nextchar
+ self.state = ' '
+ break
+ else:
+ self.state = 'a'
+ elif self.posix and nextchar in self.escape and \
+ self.state in self.escapedquotes:
+ escapedstate = self.state
+ self.state = nextchar
+ else:
+ self.token = self.token + nextchar
+ elif self.state in self.escape:
+ if not nextchar: # end of file
+ if self.debug >= 2:
+ print "shlex: I see EOF in escape state"
+ # XXX what error should be raised here?
+ raise ValueError, "No escaped character"
+ # In posix shells, only the quote itself or the escape
+ # character may be escaped within quotes.
+ if escapedstate in self.quotes and \
+ nextchar != self.state and nextchar != escapedstate:
+ self.token = self.token + self.state
+ self.token = self.token + nextchar
+ self.state = escapedstate
+ elif self.state == 'a':
+ if not nextchar:
+ self.state = None # end of file
+ break
+ elif nextchar in self.whitespace:
+ if self.debug >= 2:
+ print "shlex: I see whitespace in word state"
+ self.state = ' '
+ if self.token or (self.posix and quoted):
+ break # emit current token
+ else:
+ continue
+ elif nextchar in self.commenters:
+ self.instream.readline()
+ self.lineno = self.lineno + 1
+ if self.posix:
+ self.state = ' '
+ if self.token or (self.posix and quoted):
+ break # emit current token
+ else:
+ continue
+ elif self.posix and nextchar in self.quotes:
+ self.state = nextchar
+ elif self.posix and nextchar in self.escape:
+ escapedstate = 'a'
+ self.state = nextchar
+ elif nextchar in self.wordchars or nextchar in self.quotes \
+ or self.whitespace_split:
+ self.token = self.token + nextchar
+ else:
+ self.pushback.appendleft(nextchar)
+ if self.debug >= 2:
+ print "shlex: I see punctuation in word state"
+ self.state = ' '
+ if self.token:
+ break # emit current token
+ else:
+ continue
+ result = self.token
+ self.token = ''
+ if self.posix and not quoted and result == '':
+ result = None
+ if self.debug > 1:
+ if result:
+ print "shlex: raw token=" + repr(result)
+ else:
+ print "shlex: raw token=EOF"
+ return result
+
+ def sourcehook(self, newfile):
+ "Hook called on a filename to be sourced."
+ if newfile[0] == '"':
+ newfile = newfile[1:-1]
+ # This implements cpp-like semantics for relative-path inclusion.
+ if is_basestring(self.infile) and not os.path.isabs(newfile):
+ newfile = os.path.join(os.path.dirname(self.infile), newfile)
+ return (newfile, open(newfile, "r"))
+
+ def error_leader(self, infile=None, lineno=None):
+ "Emit a C-compiler-like, Emacs-friendly error-message leader."
+ if infile is None:
+ infile = self.infile
+ if lineno is None:
+ lineno = self.lineno
+ return "\"%s\", line %d: " % (infile, lineno)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ token = self.get_token()
+ if token == self.eof:
+ raise StopIteration
+ return token
+
+def split(s, comments=False):
+ lex = shlex(s, posix=True)
+ lex.whitespace_split = True
+ if not comments:
+ lex.commenters = ''
+ #return list(lex)
+ result = []
+ while True:
+ token = lex.get_token()
+ if token == lex.eof:
+ break
+ result.append(token)
+ return result
+
+if __name__ == '__main__':
+ if len(sys.argv) == 1:
+ lexer = shlex()
+ else:
+ file = sys.argv[1]
+ lexer = shlex(open(file), file)
+ while 1:
+ tt = lexer.get_token()
+ if tt:
+ print "Token: " + repr(tt)
+ else:
+ break
diff --git a/src/engine/SCons/cpp.py b/src/engine/SCons/cpp.py
index 8620936f..cdd6a3a9 100644
--- a/src/engine/SCons/cpp.py
+++ b/src/engine/SCons/cpp.py
@@ -45,11 +45,16 @@ import string
# that we want to fetch, using the regular expressions to which the lists
# of preprocessor directives map.
cpp_lines_dict = {
- # Fetch the rest of a #if/#elif/#ifdef/#ifndef/#import/#include/
- # #include_next line as one argument.
- ('if', 'elif', 'ifdef', 'ifndef', 'import', 'include', 'include_next',)
+ # Fetch the rest of a #if/#elif/#ifdef/#ifndef as one argument,
+ # separated from the keyword by white space.
+ ('if', 'elif', 'ifdef', 'ifndef',)
: '\s+(.+)',
+ # Fetch the rest of a #import/#include/#include_next line as one
+ # argument, with white space optional.
+ ('import', 'include', 'include_next',)
+ : '\s*(.+)',
+
# We don't care what comes after a #else or #endif line.
('else', 'endif',) : '',
@@ -183,7 +188,13 @@ class FunctionEvaluator:
"""
self.name = name
self.args = function_arg_separator.split(args)
- self.expansion = string.split(expansion, '##')
+ try:
+ expansion = string.split(expansion, '##')
+ except (AttributeError, TypeError):
+ # Python 1.5 throws TypeError if "expansion" isn't a string,
+ # later versions throw AttributeError.
+ pass
+ self.expansion = expansion
def __call__(self, *values):
"""
Evaluates the expansion of a #define macro function called
@@ -228,12 +239,14 @@ class PreProcessor:
"""
The main workhorse class for handling C pre-processing.
"""
- def __init__(self, current='.', cpppath=[], dict={}, all=0):
+ def __init__(self, current=os.curdir, cpppath=(), dict={}, all=0):
global Table
+ cpppath = tuple(cpppath)
+
self.searchpath = {
- '"' : [current] + cpppath,
- '<' : cpppath + [current],
+ '"' : (current,) + cpppath,
+ '<' : cpppath + (current,),
}
# Initialize our C preprocessor namespace for tracking the
@@ -254,7 +267,9 @@ class PreProcessor:
# stack and changing what method gets called for each relevant
# directive we might see next at this level (#else, #elif).
# #endif will simply pop the stack.
- d = {}
+ d = {
+ 'scons_current_file' : self.scons_current_file
+ }
for op in Table.keys():
d[op] = getattr(self, 'do_' + op)
self.default_table = d
@@ -278,25 +293,34 @@ class PreProcessor:
(m[0],) + t[m[0]].match(m[1]).groups(),
cpp_tuples)
- def __call__(self, contents):
+ def __call__(self, file):
+ """
+ Pre-processes a file.
+
+ This is the main public entry point.
+ """
+ self.current_file = file
+ return self.process_contents(self.read_file(file), file)
+
+ def process_contents(self, contents, fname=None):
"""
Pre-processes a file contents.
- This is the main entry point, which
+ This is the main internal entry point.
"""
self.stack = []
self.dispatch_table = self.default_table.copy()
+ self.current_file = fname
self.tuples = self.tupleize(contents)
- self.result = []
+ self.initialize_result(fname)
while self.tuples:
t = self.tuples.pop(0)
# Uncomment to see the list of tuples being processed (e.g.,
# to validate the CPP lines are being translated correctly).
#print t
self.dispatch_table[t[0]](t)
-
- return self.result
+ return self.finalize_result(fname)
# Dispatch table stack manipulation methods.
@@ -325,6 +349,9 @@ class PreProcessor:
"""
pass
+ def scons_current_file(self, t):
+ self.current_file = t[1]
+
def eval_expression(self, t):
"""
Evaluates a C preprocessor expression.
@@ -337,17 +364,29 @@ class PreProcessor:
try: return eval(t, self.cpp_namespace)
except (NameError, TypeError): return 0
+ def initialize_result(self, fname):
+ self.result = [fname]
+
+ def finalize_result(self, fname):
+ return self.result[1:]
+
def find_include_file(self, t):
"""
Finds the #include file for a given preprocessor tuple.
"""
fname = t[2]
for d in self.searchpath[t[1]]:
- f = os.path.join(d, fname)
+ if d == os.curdir:
+ f = fname
+ else:
+ f = os.path.join(d, fname)
if os.path.isfile(f):
return f
return None
+ def read_file(self, file):
+ return open(file).read()
+
# Start and stop processing include lines.
def start_handling_includes(self, t=None):
@@ -478,8 +517,10 @@ class PreProcessor:
if include_file:
#print "include_file =", include_file
self.result.append(include_file)
- contents = open(include_file).read()
- new_tuples = self.tupleize(contents)
+ contents = self.read_file(include_file)
+ new_tuples = [('scons_current_file', include_file)] + \
+ self.tupleize(contents) + \
+ [('scons_current_file', self.current_file)]
self.tuples[:] = new_tuples + self.tuples
# Date: Tue, 22 Nov 2005 20:26:09 -0500
diff --git a/src/engine/SCons/cppTests.py b/src/engine/SCons/cppTests.py
index 0959e2cc..33fd01d7 100644
--- a/src/engine/SCons/cppTests.py
+++ b/src/engine/SCons/cppTests.py
@@ -23,10 +23,10 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import string
import sys
import unittest
-print sys.path
import cpp
@@ -297,6 +297,9 @@ macro_function_input = """
#include FUNC39c(ZERO, ONE)
#include FUNC40c(ZERO, ONE)
+
+/* Make sure we don't die if the expansion isn't a string. */
+#define FUNC_INTEGER(x) 1
"""
@@ -312,6 +315,12 @@ token_pasting_input = """
"""
+no_space_input = """
+#include<file43-yes>
+#include"file44-yes"
+"""
+
+
# pp_class = PreProcessor
# #pp_class = DumbPreProcessor
@@ -331,55 +340,61 @@ class cppTestCase(unittest.TestCase):
def test_basic(self):
"""Test basic #include scanning"""
expect = self.basic_expect
- result = self.cpp(basic_input)
+ result = self.cpp.process_contents(basic_input)
assert expect == result, (expect, result)
def test_substitution(self):
"""Test substitution of #include files using CPP variables"""
expect = self.substitution_expect
- result = self.cpp(substitution_input)
+ result = self.cpp.process_contents(substitution_input)
assert expect == result, (expect, result)
def test_ifdef(self):
"""Test basic #ifdef processing"""
expect = self.ifdef_expect
- result = self.cpp(ifdef_input)
+ result = self.cpp.process_contents(ifdef_input)
assert expect == result, (expect, result)
def test_if_boolean(self):
"""Test #if with Boolean values"""
expect = self.if_boolean_expect
- result = self.cpp(if_boolean_input)
+ result = self.cpp.process_contents(if_boolean_input)
assert expect == result, (expect, result)
def test_if_defined(self):
"""Test #if defined() idioms"""
expect = self.if_defined_expect
- result = self.cpp(if_defined_input)
+ result = self.cpp.process_contents(if_defined_input)
assert expect == result, (expect, result)
def test_expression(self):
"""Test #if with arithmetic expressions"""
expect = self.expression_expect
- result = self.cpp(expression_input)
+ result = self.cpp.process_contents(expression_input)
assert expect == result, (expect, result)
def test_undef(self):
"""Test #undef handling"""
expect = self.undef_expect
- result = self.cpp(undef_input)
+ result = self.cpp.process_contents(undef_input)
assert expect == result, (expect, result)
def test_macro_function(self):
"""Test using macro functions to express file names"""
expect = self.macro_function_expect
- result = self.cpp(macro_function_input)
+ result = self.cpp.process_contents(macro_function_input)
assert expect == result, (expect, result)
def test_token_pasting(self):
- """Test taken-pasting to construct file names"""
+ """Test token-pasting to construct file names"""
expect = self.token_pasting_expect
- result = self.cpp(token_pasting_input)
+ result = self.cpp.process_contents(token_pasting_input)
+ assert expect == result, (expect, result)
+
+ def test_no_space(self):
+ """Test no space between #include and the quote"""
+ expect = self.no_space_expect
+ result = self.cpp.process_contents(no_space_input)
assert expect == result, (expect, result)
class cppAllTestCase(cppTestCase):
@@ -460,6 +475,11 @@ class PreProcessorTestCase(cppAllTestCase):
('include', '<', 'file42-yes'),
]
+ no_space_expect = [
+ ('include', '<', 'file43-yes'),
+ ('include', '"', 'file44-yes'),
+ ]
+
class DumbPreProcessorTestCase(cppAllTestCase):
cpp_class = cpp.DumbPreProcessor
@@ -560,14 +580,126 @@ class DumbPreProcessorTestCase(cppAllTestCase):
('include', '<', 'file42-yes'),
]
+ no_space_expect = [
+ ('include', '<', 'file43-yes'),
+ ('include', '"', 'file44-yes'),
+ ]
+
+
+
+import os
+import re
+import shutil
+import tempfile
+
+tempfile.template = 'cppTests.'
+if os.name in ('posix', 'nt'):
+ tempfile.template = 'cppTests.' + str(os.getpid()) + '.'
+else:
+ tempfile.template = 'cppTests.'
+
+_Cleanup = []
+
+def _clean():
+ for dir in _Cleanup:
+ if os.path.exists(dir):
+ shutil.rmtree(dir)
+
+sys.exitfunc = _clean
+
+class fileTestCase(unittest.TestCase):
+ cpp_class = cpp.DumbPreProcessor
+
+ def setUp(self):
+ try:
+ path = tempfile.mktemp(prefix=tempfile.template)
+ except TypeError:
+ # The tempfile.mktemp() function in earlier versions of Python
+ # has no prefix argument, but uses the tempfile.template
+ # value that we set above.
+ path = tempfile.mktemp()
+ _Cleanup.append(path)
+ os.mkdir(path)
+ self.tempdir = path
+ self.orig_cwd = os.getcwd()
+ os.chdir(path)
+
+ def tearDown(self):
+ shutil.rmtree(self.tempdir)
+ _Cleanup.remove(self.tempdir)
+ os.chdir(self.orig_cwd)
+
+ def strip_initial_spaces(self, s):
+ #lines = s.split('\n')
+ lines = string.split(s, '\n')
+ spaces = re.match(' *', lines[0]).group(0)
+ def strip_spaces(l, spaces=spaces):
+ #if l.startswith(spaces):
+ if l[:len(spaces)] == spaces:
+ l = l[len(spaces):]
+ return l
+ #return '\n'.join([ strip_spaces(l) for l in lines ])
+ return string.join(map(strip_spaces, lines), '\n')
+
+ def write(self, file, contents):
+ open(file, 'w').write(self.strip_initial_spaces(contents))
+
+ def test_basic(self):
+ """Test basic file inclusion"""
+ self.write('f1.h', """\
+ #include "f2.h"
+ """)
+ self.write('f2.h', """\
+ #include <f3.h>
+ """)
+ self.write('f3.h', """\
+ """)
+ p = cpp.DumbPreProcessor(current = os.curdir,
+ cpppath = [os.curdir])
+ result = p('f1.h')
+ assert result == ['f2.h', 'f3.h'], result
+
+ def test_current_file(self):
+ """Test use of the .current_file attribute"""
+ self.write('f1.h', """\
+ #include <f2.h>
+ """)
+ self.write('f2.h', """\
+ #include "f3.h"
+ """)
+ self.write('f3.h', """\
+ """)
+ class MyPreProcessor(cpp.DumbPreProcessor):
+ def __init__(self, *args, **kw):
+ apply(cpp.DumbPreProcessor.__init__, (self,) + args, kw)
+ self.files = []
+ def __call__(self, file):
+ self.files.append(file)
+ return cpp.DumbPreProcessor.__call__(self, file)
+ def scons_current_file(self, t):
+ r = cpp.DumbPreProcessor.scons_current_file(self, t)
+ self.files.append(self.current_file)
+ return r
+ p = MyPreProcessor(current = os.curdir, cpppath = [os.curdir])
+ result = p('f1.h')
+ assert result == ['f2.h', 'f3.h'], result
+ assert p.files == ['f1.h', 'f2.h', 'f3.h', 'f2.h', 'f1.h'], p.files
+
+
+
if __name__ == '__main__':
suite = unittest.TestSuite()
tclasses = [ PreProcessorTestCase,
DumbPreProcessorTestCase,
+ fileTestCase,
]
for tclass in tclasses:
names = unittest.getTestCaseNames(tclass, 'test_')
+ try:
+ names = list(set(names))
+ except NameError:
+ pass
+ names.sort()
suite.addTests(map(tclass, names))
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
-
diff --git a/src/script/scons-time.py b/src/script/scons-time.py
index 284443a4..d651ba92 100644
--- a/src/script/scons-time.py
+++ b/src/script/scons-time.py
@@ -38,6 +38,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import getopt
import glob
import os
+import os.path
import re
import shutil
import string
@@ -62,6 +63,11 @@ except NameError:
def make_temp_file(**kw):
try:
result = tempfile.mktemp(**kw)
+ try:
+ result = os.path.realpath(result)
+ except AttributeError:
+ # Python 2.1 has no os.path.realpath() method.
+ pass
except TypeError:
try:
save_template = tempfile.template
@@ -122,7 +128,12 @@ class Line:
if self.comment:
print '# %s' % self.comment
for x, y in self.points:
- print fmt % (x, y)
+ # If y is None, it usually represents some kind of break
+ # in the line's index number. We might want to represent
+ # this some way rather than just drawing the line straight
+ # between the two points on either side.
+ if not y is None:
+ print fmt % (x, y)
print 'e'
def get_x_values(self):
@@ -148,47 +159,59 @@ class Gnuplotter(Plotter):
def vertical_bar(self, x, type, label, comment):
if self.get_min_x() <= x and x <= self.get_max_x():
- points = [(x, 0), (x, self.get_max_x())]
+ points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))]
self.line(points, type, label, comment)
def get_all_x_values(self):
result = []
for line in self.lines:
result.extend(line.get_x_values())
- return result
+ return filter(None, result)
def get_all_y_values(self):
result = []
for line in self.lines:
result.extend(line.get_y_values())
- return result
+ return filter(None, result)
def get_min_x(self):
try:
return self.min_x
except AttributeError:
- self.min_x = min(self.get_all_x_values())
+ try:
+ self.min_x = min(self.get_all_x_values())
+ except ValueError:
+ self.min_x = 0
return self.min_x
def get_max_x(self):
try:
return self.max_x
except AttributeError:
- self.max_x = max(self.get_all_x_values())
+ try:
+ self.max_x = max(self.get_all_x_values())
+ except ValueError:
+ self.max_x = 0
return self.max_x
def get_min_y(self):
try:
return self.min_y
except AttributeError:
- self.min_y = min(self.get_all_y_values())
+ try:
+ self.min_y = min(self.get_all_y_values())
+ except ValueError:
+ self.min_y = 0
return self.min_y
def get_max_y(self):
try:
return self.max_y
except AttributeError:
- self.max_y = max(self.get_all_y_values())
+ try:
+ self.max_y = max(self.get_all_y_values())
+ except ValueError:
+ self.max_y = 0
return self.max_y
def draw(self):
@@ -200,10 +223,17 @@ class Gnuplotter(Plotter):
print 'set title "%s"' % self.title
print 'set key %s' % self.key_location
+ min_y = self.get_min_y()
+ max_y = self.max_graph_value(self.get_max_y())
+ range = max_y - min_y
+ incr = range / 10.0
+ start = min_y + (max_y / 2.0) + (2.0 * incr)
+ position = [ start - (i * incr) for i in xrange(5) ]
+
inx = 1
- max_y = self.max_graph_value(self.get_max_y())/2
for line in self.lines:
- line.print_label(inx, line.points[0][0]-1, max_y)
+ line.print_label(inx, line.points[0][0]-1,
+ position[(inx-1) % len(position)])
inx += 1
plot_strings = [ self.plot_string(l) for l in self.lines ]
@@ -485,6 +515,8 @@ class SConsTimer:
for file in files:
t = line_function(file, *args, **kw)
+ if t is None:
+ t = []
diff = len(columns) - len(t)
if diff > 0:
t += [''] * diff
@@ -564,7 +596,7 @@ class SConsTimer:
except ValueError:
x, type, label = bar_tuple
comment = label
- gp.vertical_bar(x, type, None, label, comment)
+ gp.vertical_bar(x, type, label, comment)
gp.draw()
@@ -613,10 +645,17 @@ class SConsTimer:
else:
search_string = time_string
contents = open(file).read()
+ if not contents:
+ sys.stderr.write('file %s has no contents!\n' % repr(file))
+ return None
result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:]
result = [ float(r) for r in result ]
if not time_string is None:
- result = result[0]
+ try:
+ result = result[0]
+ except IndexError:
+ sys.stderr.write('file %s has no results!\n' % repr(file))
+ return None
return result
def get_function_profile(self, file, function):
@@ -1294,7 +1333,12 @@ class SConsTimer:
commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest))
else:
suffix = self.archive_splitext(archive)[1]
- commands.append(self.unpack_map[suffix] + (archive,))
+ unpack_command = self.unpack_map.get(suffix)
+ if not unpack_command:
+ dest = os.path.split(archive)[1]
+ commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest))
+ else:
+ commands.append(unpack_command + (archive,))
commands.extend([
(os.chdir, 'cd %%s', self.subdir),
diff --git a/src/test_strings.py b/src/test_strings.py
index 0d6e6ac6..c446cad0 100644
--- a/src/test_strings.py
+++ b/src/test_strings.py
@@ -127,6 +127,7 @@ check_list = [
'engine/SCons/compat/_scons_optparse.py',
'engine/SCons/compat/_scons_sets.py',
'engine/SCons/compat/_scons_sets15.py',
+ 'engine/SCons/compat/_scons_shlex.py',
'engine/SCons/compat/_scons_subprocess.py',
'engine/SCons/compat/_scons_textwrap.py',
'engine/SCons/Conftest.py',
@@ -151,6 +152,7 @@ check_list = [
'engine/SCons/compat/_scons_optparse.py',
'engine/SCons/compat/_scons_sets.py',
'engine/SCons/compat/_scons_sets15.py',
+ 'engine/SCons/compat/_scons_shlex.py',
'engine/SCons/compat/_scons_subprocess.py',
'engine/SCons/compat/_scons_textwrap.py',
'engine/SCons/Conftest.py',
@@ -171,6 +173,7 @@ check_list = [
'SCons/compat/_scons_optparse.py',
'SCons/compat/_scons_sets.py',
'SCons/compat/_scons_sets15.py',
+ 'SCons/compat/_scons_shlex.py',
'SCons/compat/_scons_subprocess.py',
'SCons/compat/_scons_textwrap.py',
'SCons/Conftest.py',
@@ -214,6 +217,7 @@ check_list = [
'src/engine/SCons/compat/_scons_optparse.py',
'src/engine/SCons/compat/_scons_sets.py',
'src/engine/SCons/compat/_scons_sets15.py',
+ 'src/engine/SCons/compat/_scons_shlex.py',
'src/engine/SCons/compat/_scons_subprocess.py',
'src/engine/SCons/compat/_scons_textwrap.py',
'src/engine/SCons/Conftest.py',
diff --git a/test/Actions/function.py b/test/Actions/function.py
new file mode 100644
index 00000000..00aa688b
--- /dev/null
+++ b/test/Actions/function.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+#
+# Test that the signature of function action includes all the
+# necessary pieces.
+#
+
+test.write('SConstruct', r"""
+import re
+
+import SCons.Action
+import SCons.Builder
+
+options = Options()
+options.AddOptions(
+ ('header', 'Header string (default cell argument)', 'Head:'),
+ ('trailer', 'Trailer string (default cell argument)', 'Tail'),
+ ('NbDeps', 'Number of dependencies', '2'),
+ ('separator', 'Separator for the dependencies (function constant)', ':'),
+ ('closure_cell_value', 'Value of a closure cell', '25'),
+ ('b', 'Value of b (value default argument', '7'),
+ ('regexp', 'Regexp (object as a default argument', 'a(a*)'),
+ ('docstring', 'Docstring', 'docstring'),
+ ('extracode', 'Extra code for the builder function', ''),
+ ('extraarg', 'Extra arg builder function', ''),
+ ('nestedfuncexp', 'Expression for the nested function', 'xxx - b'),
+)
+
+optEnv = Environment(options=options, tools=[])
+
+r = re.compile(optEnv['regexp'])
+
+toto = \
+r'''
+def toto(header='%(header)s', trailer='%(trailer)s'):
+ xxx = %(closure_cell_value)s
+ def writeDeps(target, source, env, b=%(b)s, r=r %(extraarg)s ,
+ header=header, trailer=trailer, xxx=xxx):
+ """+'"""%(docstring)s"""'+"""
+ def foo(b=b, xxx=xxx):
+ return %(nestedfuncexp)s
+ f = open(str(target[0]),'wb')
+ f.write(header)
+ for d in env['ENVDEPS']:
+ f.write(d+'%(separator)s')
+ f.write(trailer+'\\n')
+ f.write(str(foo())+'\\n')
+ f.write(r.match('aaaa').group(1)+'\\n')
+ %(extracode)s
+ try:
+ f.write(str(xarg)+'\\n')
+ except NameError:
+ pass
+ f.close()
+
+ return writeDeps
+'''
+
+exec( toto % optEnv )
+
+genHeaderBld = SCons.Builder.Builder(
+ action = SCons.Action.Action(
+ toto(),
+ 'Generating $TARGET',
+ varlist=['ENVDEPS']
+ ),
+ suffix = '.gen.h'
+ )
+
+env = Environment()
+env.Append(BUILDERS = {'GenHeader' : genHeaderBld})
+
+envdeps = map(str, range(int(optEnv['NbDeps'])))
+
+env.GenHeader('Out', None, ENVDEPS=envdeps)
+""")
+
+
+rebuildstr = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+Generating Out.gen.h
+scons: done building targets.
+"""
+
+nobuildstr = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: `.' is up to date.
+scons: done building targets.
+"""
+
+def runtest( arguments, expectedOutFile, expectedRebuild=True):
+ test.run(arguments=arguments,
+ stdout=expectedRebuild and rebuildstr or nobuildstr)
+ test.must_match('Out.gen.h', expectedOutFile)
+
+ # Should not be rebuild when ran a second time with the same
+ # arguments.
+
+ test.run(arguments = arguments, stdout=nobuildstr)
+ test.must_match('Out.gen.h', expectedOutFile)
+
+
+# Original build.
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing a docstring should not cause a rebuild
+runtest('docstring=ThisBuilderDoesXAndY', """Head:0:1:Tail\n18\naaa\n""", False)
+runtest('docstring=SuperBuilder', """Head:0:1:Tail\n18\naaa\n""", False)
+runtest('docstring=', """Head:0:1:Tail\n18\naaa\n""", False)
+
+# Changing a variable listed in the varlist should cause a rebuild
+runtest('NbDeps=3', """Head:0:1:2:Tail\n18\naaa\n""")
+runtest('NbDeps=4', """Head:0:1:2:3:Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the function code should cause a rebuild
+runtest('extracode=f.write("XX\\n")', """Head:0:1:Tail\n18\naaa\nXX\n""")
+runtest('extracode=a=2', """Head:0:1:Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing a constant used in the function code should cause a rebuild
+runtest('separator=,', """Head:0,1,Tail\n18\naaa\n""")
+runtest('separator=;', """Head:0;1;Tail\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the code of a nested function should cause a rebuild
+runtest('nestedfuncexp=b-xxx', """Head:0:1:Tail\n-18\naaa\n""")
+runtest('nestedfuncexp=b+xxx', """Head:0:1:Tail\n32\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Adding an extra argument should cause a rebuild.
+runtest('extraarg=,xarg=2', """Head:0:1:Tail\n18\naaa\n2\n""")
+runtest('extraarg=,xarg=5', """Head:0:1:Tail\n18\naaa\n5\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the value of a default argument should cause a rebuild
+# case 1: a value
+runtest('b=0', """Head:0:1:Tail\n25\naaa\n""")
+runtest('b=9', """Head:0:1:Tail\n16\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# case 2: an object
+runtest('regexp=(aaaa)', """Head:0:1:Tail\n18\naaaa\n""")
+runtest('regexp=aa(a+)', """Head:0:1:Tail\n18\naa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# Changing the value of a closure cell value should cause a rebuild
+# case 1: a value
+runtest('closure_cell_value=32', """Head:0:1:Tail\n25\naaa\n""")
+runtest('closure_cell_value=7', """Head:0:1:Tail\n0\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+# case 2: a default argument
+runtest('header=MyHeader:', """MyHeader:0:1:Tail\n18\naaa\n""")
+runtest('trailer=MyTrailer', """Head:0:1:MyTrailer\n18\naaa\n""")
+runtest('', """Head:0:1:Tail\n18\naaa\n""")
+
+test.pass_test()
diff --git a/test/CPPDEFINES/basic.py b/test/CPPDEFINES/basic.py
new file mode 100644
index 00000000..5c302c33
--- /dev/null
+++ b/test/CPPDEFINES/basic.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic use of CPPPDEFINES with various data types.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+test_list = [
+ 'xyz',
+ ['x', 'y', 'z'],
+ ['x', ['y', 123], 'z', ('int', '$INTEGER')],
+ { 'c' : 3, 'b': None, 'a' : 1 },
+]
+env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0)
+for i in test_list:
+ print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
+env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1)
+for i in test_list:
+ print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
+""")
+
+expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
+ read_str = """\
+-Dxyz
+-Dx -Dy -Dz
+-Dx -Dy=123 -Dz -Dint=0
+-Da=1 -Db -Dc=3
+|xyz|
+|x| |y| |z|
+|x| |y=123| |z| |int=1|
+|a=1| |b| |c=3|
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+test.pass_test()
diff --git a/test/CPPDEFINES.py b/test/CPPDEFINES/live.py
index c38f857b..7a169e8b 100644
--- a/test/CPPDEFINES.py
+++ b/test/CPPDEFINES/live.py
@@ -25,56 +25,13 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
-XXX Put a description of the test here.
+Verify basic use of CPPDEFINES with live compilation.
"""
-import string
-
import TestSCons
test = TestSCons.TestSCons()
-# Make sure $_CPPDEFFLAGS doesn't barf when CPPDEFINES isn't defined.
-test.write('SConstruct', """\
-env = Environment()
-print env.subst('$_CPPDEFFLAGS')
-""")
-
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
- read_str = "\n")
-
-test.run(arguments = '.', stdout=expect)
-
-# Test CPPDEFINES as a string and a list.
-test.write('SConstruct', """\
-test_list = [
- 'xyz',
- ['x', 'y', 'z'],
- ['x', ['y', 123], 'z', ('int', '$INTEGER')],
- { 'c' : 3, 'b': None, 'a' : 1 },
-]
-env = Environment(CPPDEFPREFIX='-D', CPPDEFSUFFIX='', INTEGER=0)
-for i in test_list:
- print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
-env = Environment(CPPDEFPREFIX='|', CPPDEFSUFFIX='|', INTEGER=1)
-for i in test_list:
- print env.Clone(CPPDEFINES=i).subst('$_CPPDEFFLAGS')
-""")
-
-expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
- read_str = """\
--Dxyz
--Dx -Dy -Dz
--Dx -Dy=123 -Dz -Dint=0
--Da=1 -Db -Dc=3
-|xyz|
-|x| |y| |z|
-|x| |y=123| |z| |int=1|
-|a=1| |b| |c=3|
-""")
-
-test.run(arguments = '.', stdout=expect)
-
test.write('SConstruct', """\
foo = Environment(CPPDEFINES = ['FOO', ('VAL', '$VALUE')], VALUE=7)
bar = Environment(CPPDEFINES = {'BAR':None, 'VAL':8})
diff --git a/test/CPPDEFINES/scan.py b/test/CPPDEFINES/scan.py
new file mode 100644
index 00000000..c9b60c3f
--- /dev/null
+++ b/test/CPPDEFINES/scan.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the Scanner that evaluates CPP lines works as expected.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+m = 'Scanner evaluation of CPP lines not yet supported; skipping test.\n'
+test.skip_test(m)
+
+f1_exe = 'f1' + TestSCons._exe
+f2_exe = 'f2' + TestSCons._exe
+f3_exe = 'f3' + TestSCons._exe
+f4_exe = 'f4' + TestSCons._exe
+
+test.write('SConstruct', """\
+env = Environment(CPPPATH = ['.'])
+
+f1 = env.Object('f1', 'fff.c', CPPDEFINES = ['F1'])
+f2 = env.Object('f2', 'fff.c', CPPDEFINES = [('F2', 1)])
+f3 = env.Object('f3', 'fff.c', CPPDEFINES = {'F3':None})
+f4 = env.Object('f4', 'fff.c', CPPDEFINES = {'F4':1})
+
+env.Program('f1', ['prog.c', f1])
+env.Program('f2', ['prog.c', f2])
+env.Program('f3', ['prog.c', f3])
+env.Program('f4', ['prog.c', f4])
+""")
+
+test.write('f1.h', """
+#define STRING "F1"
+""")
+
+test.write('f2.h', """
+#define STRING "F2"
+""")
+
+test.write('f3.h', """
+#define STRING "F3"
+""")
+
+test.write('f4.h', """
+#define STRING "F4"
+""")
+
+test.write('fff.c', """
+#ifdef F1
+#include <f1.h>
+#endif
+#if F2
+#include <f2.h>
+#endif
+#ifdef F3
+#include <f3.h>
+#endif
+#ifdef F4
+#include <f4.h>
+#endif
+
+char *
+foo(void)
+{
+ return (STRING);
+}
+""")
+
+
+test.write('prog.c', r"""
+#include <stdio.h>
+#include <stdlib.h>
+
+extern char *foo(void);
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("prog.c: %s\n", foo());
+ exit (0);
+}
+""")
+
+
+
+test.run(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c: F1\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c: F2\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
+
+
+
+test.write('f1.h', """
+#define STRING "F1 again"
+""")
+
+test.up_to_date(arguments = '%(f2_exe)s %(f3_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c: F2\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
+
+
+
+test.write('f2.h', """
+#define STRING "F2 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f3_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c: F3\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
+
+
+
+test.write('f3.h', """
+#define STRING "F3 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f4_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c: F3 again\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c: F4\n")
+
+
+
+test.write('f4.h', """
+#define STRING "F4 again"
+""")
+
+test.up_to_date(arguments = '%(f1_exe)s %(f2_exe)s %(f3_exe)s' % locals())
+
+test.not_up_to_date(arguments = '.')
+
+test.run(program = test.workpath('f1'), stdout = "prog.c: F1 again\n")
+test.run(program = test.workpath('f2'), stdout = "prog.c: F2 again\n")
+test.run(program = test.workpath('f3'), stdout = "prog.c: F3 again\n")
+test.run(program = test.workpath('f4'), stdout = "prog.c: F4 again\n")
+
+
+
+test.pass_test()
diff --git a/test/CPPDEFINES/undefined.py b/test/CPPDEFINES/undefined.py
new file mode 100644
index 00000000..b6b8b445
--- /dev/null
+++ b/test/CPPDEFINES/undefined.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that $_CPPDEFFLAGS doesn't barf when CPPDEFINES isn't defined.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+print env.subst('$_CPPDEFFLAGS')
+""")
+
+expect = test.wrap_stdout(build_str="scons: `.' is up to date.\n",
+ read_str = "\n")
+
+test.run(arguments = '.', stdout=expect)
+
+test.pass_test()
diff --git a/test/CPPPATH/absolute-path.py b/test/CPPPATH/absolute-path.py
new file mode 100644
index 00000000..9adb206d
--- /dev/null
+++ b/test/CPPPATH/absolute-path.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability to #include a file with an absolute path name. (Which
+is not strictly a test of using $CPPPATH, but it's in the ball park...)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('include', 'work')
+
+inc_h = test.workpath('include', 'inc.h')
+does_not_exist_h = test.workpath('include', 'does_not_exist.h')
+
+test.write(['work', 'SConstruct'], """\
+Program('prog.c')
+""")
+
+test.write(['work', 'prog.c'], """\
+#include <stdio.h>
+#include "%(inc_h)s"
+#if 0
+#include "%(does_not_exist_h)s"
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("%%s\\n", STRING);
+ return 0;
+}
+""" % locals())
+
+test.write(['include', 'inc.h'], """\
+#define STRING "include/inc.h 1\\n"
+""")
+
+test.run(chdir = 'work', arguments = '.')
+
+test.up_to_date(chdir = 'work', arguments = '.')
+
+test.write(['include', 'inc.h'], """\
+#define STRING "include/inc.h 2\\n"
+""")
+
+test.not_up_to_date(chdir = 'work', arguments = '.')
+
+test.pass_test()
diff --git a/test/CPPPATH/function-expansion.py b/test/CPPPATH/function-expansion.py
new file mode 100644
index 00000000..7c80f22c
--- /dev/null
+++ b/test/CPPPATH/function-expansion.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that expansion of construction variables whose values are functions
+(that return lists that contain Nodes, even) works as expected within
+a $CPPPATH list definition.
+
+This used to cause TypeErrors when the _concat expansion tried to
+join the Nodes with the strings.
+
+Test courtesy Konstantin Bozhikov.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('list_inc1',
+ 'list_inc2',
+ 'inc1',
+ 'inc2',
+ 'inc3',
+ ['inc3', 'subdir'])
+
+test.write('SConstruct', """\
+env = Environment()
+def my_cpppaths( target, source, env, for_signature ):
+ return [ Dir('list_inc1'), Dir('list_inc2') ]
+
+env = Environment( CPPPATH = [Dir('inc1'), '$INC2', '$INC3/subdir', '$MY_CPPPATHS' ],
+ INC2 = Dir('inc2'),
+ INC3 = Dir('inc3'),
+ MY_CPPPATHS = my_cpppaths )
+
+env.Program('prog.c')
+""")
+
+test.write('prog.c', """\
+#include <stdio.h>
+#include <stdlib.h>
+#include "string_list_1.h"
+#include "string_list_2.h"
+#include "string_1.h"
+#include "string_2.h"
+#include "string_3.h"
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("prog.c\\n");
+ printf("%s\\n", STRING_LIST_1);
+ printf("%s\\n", STRING_LIST_2);
+ printf("%s\\n", STRING_1);
+ printf("%s\\n", STRING_2);
+ printf("%s\\n", STRING_3);
+ exit (0);
+}
+""")
+
+test.write(['list_inc1', 'string_list_1.h'], """\
+#define STRING_LIST_1 "list_inc1/string_list_1.h"
+""")
+
+test.write(['list_inc2', 'string_list_2.h'], """\
+#define STRING_LIST_2 "list_inc2/string_list_2.h"
+""")
+
+test.write(['inc1', 'string_1.h'], """\
+#define STRING_1 "inc1/string_1.h"
+""")
+
+test.write(['inc2', 'string_2.h'], """\
+#define STRING_2 "inc2/string_2.h"
+""")
+
+test.write(['inc3', 'subdir', 'string_3.h'], """\
+#define STRING_3 "inc3/subdir/string_3.h"
+""")
+
+test.run()
+
+test.up_to_date(arguments = '.')
+
+expect = """\
+prog.c
+list_inc1/string_list_1.h
+list_inc2/string_list_2.h
+inc1/string_1.h
+inc2/string_2.h
+inc3/subdir/string_3.h
+"""
+
+test.run(program = test.workpath('prog' + TestSCons._exe), stdout=expect)
+
+test.write(['inc3', 'subdir', 'string_3.h'], """\
+#define STRING_3 "inc3/subdir/string_3.h 2"
+""")
+
+test.not_up_to_date(arguments = '.')
+
+expect = """\
+prog.c
+list_inc1/string_list_1.h
+list_inc2/string_list_2.h
+inc1/string_1.h
+inc2/string_2.h
+inc3/subdir/string_3.h 2
+"""
+
+test.run(program = test.workpath('prog' + TestSCons._exe), stdout=expect)
+
+test.pass_test()
diff --git a/test/CPPPATH/list-expansion.py b/test/CPPPATH/list-expansion.py
new file mode 100644
index 00000000..7e5326f1
--- /dev/null
+++ b/test/CPPPATH/list-expansion.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that expansion of construction variables whose values are
+lists works as expected within a $CPPPATH list definition.
+
+Previously, the stringification of the expansion of the individual
+variables would turn a list like ['sub1', 'sub2'] below into "-Isub1 sub2"
+on the command line.
+
+Test case courtesy Daniel Svensson.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('sub1', 'sub2', 'sub3', 'sub4')
+
+test.write('SConstruct', """\
+class _inc_test:
+ def __init__(self, name):
+ self.name = name
+
+ def __call__(self, target, source, env, for_signature):
+ return env.something[self.name]
+
+env = Environment()
+
+env.something = {}
+env.something['test'] = ['sub1', 'sub2']
+
+env['INC_PATHS1'] = _inc_test
+
+env['INC_PATHS2'] = ['sub3', 'sub4']
+
+env.Append(CPPPATH = ['${INC_PATHS1("test")}', '$INC_PATHS2'])
+env.Program('test', 'test.c')
+""")
+
+test.write('test.c', """\
+#include <stdio.h>
+#include <stdlib.h>
+#include "string1.h"
+#include "string2.h"
+#include "string3.h"
+#include "string4.h"
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("test.c\\n");
+ printf("%s\\n", STRING1);
+ printf("%s\\n", STRING2);
+ printf("%s\\n", STRING3);
+ printf("%s\\n", STRING4);
+ exit (0);
+}
+""")
+
+test.write(['sub1', 'string1.h'], """\
+#define STRING1 "sub1/string1.h"
+""")
+
+test.write(['sub2', 'string2.h'], """\
+#define STRING2 "sub2/string2.h"
+""")
+
+test.write(['sub3', 'string3.h'], """\
+#define STRING3 "sub3/string3.h"
+""")
+
+test.write(['sub4', 'string4.h'], """\
+#define STRING4 "sub4/string4.h"
+""")
+
+test.run()
+
+test.up_to_date(arguments = '.')
+
+expect = """\
+test.c
+sub1/string1.h
+sub2/string2.h
+sub3/string3.h
+sub4/string4.h
+"""
+
+test.run(program = test.workpath('test' + TestSCons._exe), stdout=expect)
+
+test.write(['sub2', 'string2.h'], """\
+#define STRING2 "sub2/string2.h 2"
+""")
+
+test.not_up_to_date(arguments = '.')
+
+expect = """\
+test.c
+sub1/string1.h
+sub2/string2.h 2
+sub3/string3.h
+sub4/string4.h
+"""
+
+test.run(program = test.workpath('test' + TestSCons._exe), stdout=expect)
+
+test.pass_test()
diff --git a/test/Configure/Builder-call.py b/test/Configure/Builder-call.py
index a6f2fa5c..1ce114c6 100644
--- a/test/Configure/Builder-call.py
+++ b/test/Configure/Builder-call.py
@@ -35,7 +35,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-test.write('cmd.py', r"""
+test.write('mycommand.py', r"""
import sys
sys.stderr.write( 'Hello World on stderr\n' )
sys.stdout.write( 'Hello World on stdout\n' )
@@ -48,7 +48,7 @@ def CustomTest(*args):
return 0
conf = env.Configure(custom_tests = {'MyTest' : CustomTest})
if not conf.MyTest():
- env.Command("hello", [], '%(_python_)s cmd.py $TARGET')
+ env.Command("hello", [], '%(_python_)s mycommand.py $TARGET')
env = conf.Finish()
""" % locals())
diff --git a/test/DMD.py b/test/D/DMD.py
index 8b443f89..8b443f89 100644
--- a/test/DMD.py
+++ b/test/D/DMD.py
diff --git a/test/D/Scanner.py b/test/D/Scanner.py
new file mode 100644
index 00000000..5c698204
--- /dev/null
+++ b/test/D/Scanner.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the D scanner can return multiple modules imported by
+a single statement.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_obj = TestSCons._obj
+
+dmd = test.where_is('dmd')
+if not dmd:
+ test.skip_test("Could not find 'dmd'; skipping test.\n")
+
+test.subdir(['p'])
+
+test.write('SConstruct', """
+env = Environment()
+env.Program('test1.d')
+env.Program('test2.d')
+""")
+
+test.write(['test1.d'], """\
+import module1;
+import module2;
+import module3;
+import p.submodule1;
+import p.submodule2;
+
+int main() {
+ return 0;
+}
+""")
+
+test.write(['test2.d'], """\
+import
+ module1,
+ module2,
+ module3;
+import
+ p.submodule1,
+ p.submodule2;
+
+int main() {
+ return 0;
+}
+""")
+
+test.write(['ignored.d'], """\
+module ignored;
+
+int something;
+""")
+
+test.write(['module1.d'], """\
+module module1;
+
+int something;
+""")
+
+test.write(['module2.d'], """\
+module module2;
+
+int something;
+""")
+
+test.write(['module3.di'], """\
+module module3;
+
+int something;
+""")
+
+test.write(['p', 'ignored.d'], """\
+module p.ignored;
+
+int something;
+""")
+
+test.write(['p', 'submodule1.d'], """\
+module p.submodule1;
+
+int something;
+""")
+
+test.write(['p', 'submodule2.d'], """\
+module p.submodule2;
+
+int something;
+""")
+
+arguments = 'test1%(_obj)s test2%(_obj)s' % locals()
+
+test.run(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.write(['module2.d'], """\
+module module2;
+
+int something_else;
+""")
+
+test.not_up_to_date(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.write(['p', 'submodule2.d'], """\
+module p.submodule2;
+
+int something_else;
+""")
+
+test.not_up_to_date(arguments = arguments)
+
+test.up_to_date(arguments = arguments)
+
+test.pass_test()
diff --git a/test/Errors/SyntaxError.py b/test/Errors/SyntaxError.py
index b9ff1ffd..956caa7d 100644
--- a/test/Errors/SyntaxError.py
+++ b/test/Errors/SyntaxError.py
@@ -37,8 +37,10 @@ test.write('SConstruct', """
a ! x
""")
+# It looks like vanilla Python 2.2 is the only version that
+# puts "<string>" here in place of the file name.
test.run(stdout = "scons: Reading SConscript files ...\n",
- stderr = """ File ".+SConstruct", line 2
+ stderr = """ File "(.+SConstruct|<string>)", line 2
a ! x
diff --git a/test/Errors/execute-a-directory.py b/test/Errors/execute-a-directory.py
new file mode 100644
index 00000000..bcdcb7cb
--- /dev/null
+++ b/test/Errors/execute-a-directory.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+not_executable = test.workpath("not_executable")
+
+test.write(not_executable, "\n")
+
+test.write("f3.in", "\n")
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld' : bld })
+env.bld(target = 'f3', source = 'f3.in')
+""" % string.replace(test.workdir, '\\', '\\\\'))
+
+test.run(arguments='.',
+ stdout = test.wrap_stdout("%s f3.in f3\n" % test.workdir, error=1),
+ stderr = None,
+ status = 2)
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+cannot_execute = """\
+%s: cannot execute
+scons *** [%s] Error 126
+"""
+
+Permission_denied = """\
+%s: Permission denied
+scons: *** [%s] Error 126
+"""
+
+permission_denied = """\
+%s: permission denied
+scons: *** [%s] Error 126
+"""
+
+is_a_directory = """\
+%s: is a directory
+scons: *** [%s] Error 126
+"""
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+ errs = [
+ bad_command,
+ unrecognized % (test.workdir, 'f3'),
+ unspecified % 'f3'
+ ]
+ test.fail_test(not test.stderr() in errs)
+else:
+ errs = [
+ cannot_execute % (not_executable, 'f3'),
+ is_a_directory % (test.workdir, 'f3'),
+ Permission_denied % (test.workdir, 'f3'),
+ Permission_denied % (test.workdir, 'f3'),
+ ]
+ error_message_not_found = 1
+ for err in errs:
+ if string.find(test.stderr(), err) != -1:
+ error_message_not_found = None
+ break
+ test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/non-executable-file.py b/test/Errors/non-executable-file.py
new file mode 100644
index 00000000..d6e018b9
--- /dev/null
+++ b/test/Errors/non-executable-file.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+not_executable = test.workpath("not_executable")
+
+test.write(not_executable, "\n")
+
+test.write("f1.in", "\n")
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+cannot_execute = """\
+%s: cannot execute
+scons *** [%s] Error 126
+"""
+
+Permission_denied = """\
+%s: Permission denied
+scons: *** [%s] Error 126
+"""
+
+permission_denied = """\
+%s: permission denied
+scons: *** [%s] Error 126
+"""
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld': bld })
+env.bld(target = 'f1', source = 'f1.in')
+""" % string.replace(not_executable, '\\', '\\\\'))
+
+test.run(arguments='.',
+ stdout = test.wrap_stdout("%s f1.in f1\n" % not_executable, error=1),
+ stderr = None,
+ status = 2)
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+ errs = [
+ bad_command,
+ unrecognized % (not_executable, 'f1'),
+ unspecified % 'f1'
+ ]
+ test.fail_test(not test.stderr() in errs)
+else:
+ errs = [
+ cannot_execute % (not_executable, 'f1'),
+ Permission_denied % (not_executable, 'f1'),
+ permission_denied % (not_executable, 'f1'),
+ ]
+ error_message_not_found = 1
+ for err in errs:
+ if string.find(test.stderr(), err) != -1:
+ error_message_not_found = None
+ break
+ test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/nonexistent-executable.py b/test/Errors/nonexistent-executable.py
new file mode 100644
index 00000000..b2a9557d
--- /dev/null
+++ b/test/Errors/nonexistent-executable.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+no_such_file = test.workpath("no_such_file")
+
+test.write("f1.in", "\n")
+
+test.write('SConstruct', r"""
+bld = Builder(action = '%s $SOURCES $TARGET')
+env = Environment(BUILDERS = { 'bld' : bld })
+env.bld(target = 'f1', source = 'f1.in')
+""" % string.replace(no_such_file, '\\', '\\\\'))
+
+test.run(arguments='.',
+ stdout = test.wrap_stdout("%s f1.in f1\n" % no_such_file, error=1),
+ stderr = None,
+ status = 2)
+
+bad_command = """\
+Bad command or file name
+"""
+
+unrecognized = """\
+'%s' is not recognized as an internal or external command,
+operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+unspecified = """\
+The name specified is not recognized as an
+internal or external command, operable program or batch file.
+scons: *** [%s] Error 1
+"""
+
+not_found_1 = """
+sh: %s: not found
+scons: *** [%s] Error 1
+"""
+
+not_found_2 = """
+sh: %s: not found
+scons: *** [%s] Error 1
+"""
+
+not_found_127 = """\
+sh: %s: not found
+scons: *** [%s] Error 127
+"""
+
+No_such = """\
+%s: No such file or directory
+scons: *** [%s] Error 127
+"""
+
+test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
+if os.name == 'nt':
+ errs = [
+ bad_command,
+ unrecognized % (no_such_file, 'f1'),
+ unspecified % 'f1'
+ ]
+ test.fail_test(not test.stderr() in errs)
+else:
+ errs = [
+ not_found_1 % (no_such_file, 'f1'),
+ not_found_2 % (no_such_file, 'f1'),
+ not_found_127 % (no_such_file, 'f1'),
+ No_such % (no_such_file, 'f1'),
+ ]
+ error_message_not_found = 1
+ for err in errs:
+ if string.find(test.stderr(), err) != -1:
+ error_message_not_found = None
+ break
+ test.fail_test(error_message_not_found)
+
+test.pass_test()
diff --git a/test/Errors/permission-denied.py b/test/Errors/permission-denied.py
new file mode 100644
index 00000000..05d1e9d5
--- /dev/null
+++ b/test/Errors/permission-denied.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', r"""
+env = Environment()
+env.Command('test.out', 'test.in', Copy('$TARGET', '$SOURCE'))
+env.InstallAs('test2.out', 'test.out')
+# Mark test2.out as precious so we'll handle the exception in
+# FunctionAction() rather than when the target is cleaned before building.
+env.Precious('test2.out')
+env.Default('test2.out')
+""")
+
+test.write('test.in', "test.in 1\n")
+
+test.run(arguments = '.')
+
+test.write('test.in', "test.in 2\n")
+
+test.writable('test2.out', 0)
+f = open(test.workpath('test2.out'))
+
+test.run(arguments = '.',
+ stderr = None,
+ status = 2)
+
+f.close()
+test.writable('test2.out', 1)
+
+test.description_set("Incorrect STDERR:\n%s" % test.stderr())
+errs = [
+ "scons: *** [test2.out] test2.out: Permission denied\n",
+ "scons: *** [test2.out] test2.out: permission denied\n",
+]
+test.fail_test(test.stderr() not in errs)
+
+test.pass_test()
diff --git a/test/Install/option--install-sandbox.py b/test/Install/option--install-sandbox.py
index 4cf9310f..38a7915c 100644
--- a/test/Install/option--install-sandbox.py
+++ b/test/Install/option--install-sandbox.py
@@ -48,9 +48,10 @@ file1_out = target+os.path.join( target, destdir, 'file1.out' )
#
test.write('SConstruct', r"""
env = Environment(SUBDIR='subdir')
-env.Install(r'%(destdir)s', 'file1.out')
-env.InstallAs(['file2.out', r'%(_SUBDIR_file3_out)s'],
- ['file2.in', r'%(_SUBDIR_file3_in)s'])
+f1 = env.Install(r'%(destdir)s', 'file1.out')
+f2 = env.InstallAs(['file2.out', r'%(_SUBDIR_file3_out)s'],
+ ['file2.in', r'%(_SUBDIR_file3_in)s'])
+env.Depends(f2, f1)
""" % locals())
test.write('file1.out', "file1.out\n")
@@ -58,9 +59,9 @@ test.write('file2.in', "file2.in\n")
test.write(['subdir', 'file3.in'], "subdir/file3.in\n")
expect = test.wrap_stdout("""\
+Install file: "file1.out" as "%(file1_out)s"
Install file: "file2.in" as "%(target_file2_out)s"
Install file: "%(subdir_file3_in)s" as "%(target_subdir_file3_out)s"
-Install file: "file1.out" as "%(file1_out)s"
""" % locals())
test.run(arguments = '--install-sandbox=%s' % destdir, stdout=expect)
diff --git a/test/Interactive/Alias.py b/test/Interactive/Alias.py
new file mode 100644
index 00000000..fc05b9ab
--- /dev/null
+++ b/test/Interactive/Alias.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability to build an Alias in --interactive mode.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+foo = Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Alias('foo-alias', foo)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo-alias\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmyn for the "build" command.
+scons.send("scons foo-alias\n")
+
+scons.send("scons 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo-alias\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo-alias' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/Default-None.py b/test/Interactive/Default-None.py
new file mode 100644
index 00000000..36ebf2fb
--- /dev/null
+++ b/test/Interactive/Default-None.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we get the expected error message when we use a "build"
+command without arguments and there are no default targets (because they
+explicitly called Default(None) in the SConstruct file).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Default(None)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build\n")
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmyn for the "build" command.
+scons.send("scons\n")
+
+scons.send("scons foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build\n")
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> scons: *** No targets specified and no Default() targets found. Stop.
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> scons: *** No targets specified and no Default() targets found. Stop.
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: *** No targets specified and no Default() targets found. Stop.
+scons>>> scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/Default.py b/test/Interactive/Default.py
new file mode 100644
index 00000000..d6205e29
--- /dev/null
+++ b/test/Interactive/Default.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we can use a "build" command without arguments to (re-)build
+the Default() targets, without exiting the command loop.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+foo_out = Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Default(foo_out)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmy for the "build" command.
+scons.send("scons\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/added-include.py b/test/Interactive/added-include.py
new file mode 100644
index 00000000..8c303146
--- /dev/null
+++ b/test/Interactive/added-include.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This verifies the --interactive command line option's ability to
+rebuild a target when an implicit dependency (include line) is
+added to the source file.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('foo.h.in', """
+#define FOO_STRING "foo.h.in"
+""")
+
+test.write('foo.c', """
+#include <stdio.h>
+
+int main()
+{
+ printf("foo.c\\n");
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+env = Environment(CPPPATH=['.'])
+env.Command('foo.h', ['foo.h.in'], Copy('$TARGET', '$SOURCE'))
+env.Program('foo', ['foo.c'])
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+foo_exe = test.workpath('foo' + TestSCons._exe)
+_foo_exe_ = '"%s"' % string.replace(foo_exe, '\\', '\\\\')
+
+
+
+# Start scons, to build "foo"
+scons = test.start(arguments = '--interactive')
+
+scons.send("build %(_foo_exe_)s 1\n" % locals())
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.run(program = foo_exe, stdout = 'foo.c\n')
+
+
+
+
+# Update foo.c
+# We add a new #include line, to make sure that scons notices
+# the new implicit dependency and builds foo.h first.
+test.write('foo.c', """
+#include <foo.h>
+
+#include <stdio.h>
+
+int main()
+{
+ printf("%s\\n", FOO_STRING);
+ return 0;
+}
+""")
+
+scons.send("build %(_foo_exe_)s 2\n" % locals())
+
+test.wait_for(test.workpath('2'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = foo_exe, stdout = 'foo.h.in\n')
+
+
+
+test.write('foo.h.in', """
+#define FOO_STRING "foo.h.in 3"
+""")
+
+
+
+scons.send("build %(_foo_exe_)s 3\n" % locals())
+
+test.wait_for(test.workpath('3'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = foo_exe, stdout = 'foo.h.in 3\n')
+
+
+
+test.pass_test()
diff --git a/test/Interactive/basic.py b/test/Interactive/basic.py
new file mode 100644
index 00000000..4c78f69c
--- /dev/null
+++ b/test/Interactive/basic.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option to build
+a target, rebuild it when the input changes, and not rebuild it when
+the input doesn't change.
+
+Also tests that "scons" can be used as a synonym for "build".
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+# Verify that "scons" can be used as a synonmy for the "build" command.
+scons.send("scons foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-debug.py b/test/Interactive/cache-debug.py
new file mode 100644
index 00000000..dab51596
--- /dev/null
+++ b/test/Interactive/cache-debug.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-debug option is used.
+"""
+
+import TestCmd
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-debug=- foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = \
+r"""scons>>> Copy\("foo.out", "foo.in"\)
+scons>>> Touch\("1"\)
+scons>>> Removed foo.out
+scons>>> Touch\("2"\)
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch\("3"\)
+scons>>> Removed foo.out
+scons>>> Touch\("4"\)
+scons>>> Retrieved `foo.out' from cache
+CacheRetrieve\(foo.out\): retrieving from [0-9A-za-z]+
+scons>>> Touch\("5"\)
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout, match=TestCmd.match_re)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-disable.py b/test/Interactive/cache-disable.py
new file mode 100644
index 00000000..0fb8435c
--- /dev/null
+++ b/test/Interactive/cache-disable.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build
+a target when the --cache-disable option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-disable foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> Removed foo.out
+scons>>> Touch("4")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("5")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-force.py b/test/Interactive/cache-force.py
new file mode 100644
index 00000000..8fd3f920
--- /dev/null
+++ b/test/Interactive/cache-force.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-force option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+import shutil
+shutil.rmtree(test.workpath('cache'))
+
+
+
+scons.send("build --cache-force foo.out\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> scons: `foo.out' is up to date.
+scons>>> Removed foo.out
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("4")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/cache-show.py b/test/Interactive/cache-show.py
new file mode 100644
index 00000000..c1fe4873
--- /dev/null
+++ b/test/Interactive/cache-show.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the --interactive command line option to build a target when the
+--cache-show option is used.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+CacheDir('cache')
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+Command('4', [], Touch('$TARGET'))
+Command('5', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+scons.send("clean foo.out\n")
+
+scons.send("build 4\n")
+
+test.wait_for(test.workpath('4'))
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build --cache-show foo.out\n")
+
+scons.send("build 5\n")
+
+test.wait_for(test.workpath('5'))
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Removed foo.out
+scons>>> Touch("2")
+scons>>> Retrieved `foo.out' from cache
+scons>>> Touch("3")
+scons>>> Removed foo.out
+scons>>> Touch("4")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("5")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/clean.py b/test/Interactive/clean.py
new file mode 100644
index 00000000..4f4f80de
--- /dev/null
+++ b/test/Interactive/clean.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verifies operation of the --interactive command line option
+"clean" subcommand.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Command('f3.out', 'f3.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build f1.out f2.out f3.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('f1.out'), "f1.in\n")
+test.must_match(test.workpath('f2.out'), "f2.in\n")
+test.must_match(test.workpath('f3.out'), "f3.in\n")
+
+
+
+scons.send("clean f1.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist('f1.out')
+test.must_exist('f2.out')
+test.must_exist('f3.out')
+
+
+
+scons.send("build -c\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_not_exist('f1.out')
+test.must_not_exist('f2.out')
+test.must_not_exist('f3.out')
+
+expect_stdout = """\
+scons>>> Copy("f1.out", "f1.in")
+Copy("f2.out", "f2.in")
+Copy("f3.out", "f3.in")
+scons>>> Touch("1")
+scons>>> Removed f1.out
+scons>>> Touch("2")
+scons>>> Removed 1
+Removed 2
+Removed f2.out
+Removed f3.out
+scons>>> Touch("3")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/exit.py b/test/Interactive/exit.py
new file mode 100644
index 00000000..df06d5da
--- /dev/null
+++ b/test/Interactive/exit.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify use of the "exit" subcommand.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('exit\n')
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> """
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/help.py b/test/Interactive/help.py
new file mode 100644
index 00000000..ef4c5783
--- /dev/null
+++ b/test/Interactive/help.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the behavior of the "help" subcommand (and its "h" and "?" aliases).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('help\n')
+
+scons.send('h\n')
+
+scons.send('?\n')
+
+help_text = """\
+build [TARGETS] Build the specified TARGETS and their dependencies.
+ 'b' is a synonym.
+clean [TARGETS] Clean (remove) the specified TARGETS and their
+ dependencies. 'c' is a synonym.
+exit Exit SCons interactive mode.
+help [COMMAND] Prints help for the specified COMMAND. 'h' and
+ '?' are synonyms.
+shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!'
+ are synonyms.
+version Prints SCons version information.
+"""
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> %(help_text)s
+scons>>> %(help_text)s
+scons>>> %(help_text)s
+scons>>>
+""" % locals()
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/implicit-BuildDir.py b/test/Interactive/implicit-BuildDir.py
new file mode 100644
index 00000000..7b7aa4b4
--- /dev/null
+++ b/test/Interactive/implicit-BuildDir.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+This is a regression test for a bug in earlier versions of the
+--interactive command line option (specifically the original prototype
+submitted by Adam Simpkins, who created this test case).
+
+It tests to make sure that cached state is cleared between files for
+nodes in both the build tree and the source tree when BuildDirs are used.
+This is needed especially with BuildDirs created with duplicate=0, since
+the scanners scan the files in the source tree. Any cached implicit
+deps must be cleared on the source files.
+"""
+
+import os.path
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src',
+ ['src', 'inc'])
+
+# Create the top-level SConstruct file
+test.write('SConstruct', """
+BUILD_ENV = Environment()
+Export('BUILD_ENV')
+
+hdr_dir = '#build/include'
+BUILD_ENV['HDR_DIR'] = hdr_dir
+BUILD_ENV.Append(CPPPATH = hdr_dir)
+
+BUILD_ENV.BuildDir('build', 'src', duplicate = 0)
+SConscript('build/SConscript')
+
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+# Create the src/SConscript file
+test.write(['src', 'SConscript'], """
+Import('BUILD_ENV')
+BUILD_ENV.Install(BUILD_ENV['HDR_DIR'], ['inc/foo.h'])
+BUILD_ENV.Program('foo', ['foo.c'])
+""")
+
+# Create src/foo.c
+test.write(['src', 'foo.c'], """
+#include <stdio.h>
+
+#define FOO_PRINT_STRING "Hello from foo.c"
+
+int main()
+{
+ printf(FOO_PRINT_STRING "\\n");
+ return 0;
+}
+""")
+
+# Create src/inc/foo.h
+test.write(['src', 'inc', 'foo.h'], """
+#ifndef INCLUDED_foo_h
+#define INCLUDED_foo_h
+
+#define FOO_PRINT_STRING "Hello from foo.h"
+
+#endif /* INCLUDED_foo_h */
+""")
+
+# Start scons, to build only "build/foo"
+build_foo_exe = os.path.join('build', 'foo' + TestSCons._exe)
+_build_foo_exe_ = '"%s"' % string.replace(build_foo_exe, '\\', '\\\\')
+abs_foo_exe = test.workpath(build_foo_exe)
+
+scons = test.start(arguments = '--interactive', combine=1)
+
+
+
+# Build build/foo
+scons.send('build %(_build_foo_exe_)s 1\n' % locals())
+
+test.wait_for(test.workpath('1'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = abs_foo_exe, stdout = 'Hello from foo.c\n')
+
+
+
+# Update foo.c to include foo.h
+test.write(['src', 'foo.c'], """
+#include "foo.h"
+#include <stdio.h>
+
+int main()
+{
+ printf(FOO_PRINT_STRING "\\n");
+ return 0;
+}
+""")
+
+# Build build/foo
+scons.send('build %(_build_foo_exe_)s 2\n' % locals())
+
+test.wait_for(test.workpath('2'))
+
+# Run foo, and make sure it prints correctly
+test.run(program = abs_foo_exe, stdout = 'Hello from foo.h\n')
+
+
+
+scons.send('exit\n')
+
+test.finish(scons)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option--Q.py b/test/Interactive/option--Q.py
new file mode 100644
index 00000000..4c02fa72
--- /dev/null
+++ b/test/Interactive/option--Q.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that use of the -Q option on an individual "build" command
+will suppress the progress messages.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '--interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build -Q foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons>>> scons: Building targets ...
+Copy("foo.out", "foo.in")
+Touch("1")
+scons: done building targets.
+scons: Clearing cached node information ...
+scons: done clearing node information.
+scons>>> Copy("foo.out", "foo.in")
+Touch("2")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-i.py b/test/Interactive/option-i.py
new file mode 100644
index 00000000..a2935a70
--- /dev/null
+++ b/test/Interactive/option-i.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -i option, specified on the build command, causes
+build errors to be ignored, just for that command.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def error(target, source, env):
+ return 1
+e1 = Command('e1.out', 'e1.in', Action(error))
+e2 = Command('e2.out', 'e2.in', Action(error))
+f1 = Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+f2 = Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Depends(f1, e1)
+Depends(f2, e2)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('e1.in', "e1.in\n")
+test.write('e2.in', "e2.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive', combine=1)
+
+scons.send("build f1.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+
+
+
+scons.send("build -i e1.out f1.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('f1.out'), "f1.in\n")
+
+
+
+scons.send("build f2.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_not_exist(test.workpath('f2.out'))
+
+
+
+expect_stdout = """\
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("1")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+Copy("f1.out", "f1.in")
+scons>>> Touch("2")
+scons>>> error(["e2.out"], ["e2.in"])
+scons: *** [e2.out] Error 1
+scons>>> Touch("3")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-j.py b/test/Interactive/option-j.py
new file mode 100644
index 00000000..29bba88b
--- /dev/null
+++ b/test/Interactive/option-j.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that "build" command of --interactive mode can take a -j
+option to build things in parallel.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+test.write('SConstruct', """\
+import os
+import time
+from SCons.Script import *
+def cat(target, source, env):
+ t = str(target[0])
+ os.mkdir(t + '.started')
+ fp = open(t, 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+ os.mkdir(t + '.finished')
+
+def must_be_finished(target, source, env, dir):
+ if not os.path.exists(dir):
+ msg = 'build failed, %s does not exist\\n' % dir
+ sys.stderr.write(msg)
+ return 1
+ return cat(target, source, env)
+
+def f1_a_out_must_be_finished(target, source, env):
+ return must_be_finished(target, source, env, 'f1-a.out.finished')
+def f3_a_out_must_be_finished(target, source, env):
+ return must_be_finished(target, source, env, 'f3-a.out.finished')
+
+def must_wait_for_f2_b_out(target, source, env):
+ t = str(target[0])
+ os.mkdir(t + '.started')
+ f2_b_started = 'f2-b.out.started'
+ while not os.path.exists(f2_b_started):
+ time.sleep(1)
+ fp = open(t, 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+ os.mkdir(t + '.finished')
+
+def _f2_a_out_must_not_be_finished(target, source, env):
+ f2_a_started = 'f2-a.out.started'
+ f2_a_finished = 'f2-a.out.finished'
+ while not os.path.exists(f2_a_started):
+ time.sleep(1)
+ msg = 'f2_a_out_must_not_be_finished(["%s"], ["%s"])\\n' % (target[0], source[0])
+ sys.stdout.write(msg)
+ if os.path.exists(f2_a_finished):
+ msg = 'build failed, %s exists\\n' % f2_a_finished
+ sys.stderr.write(msg)
+ return 1
+ return cat(target, source, env)
+
+f2_a_out_must_not_be_finished = Action(_f2_a_out_must_not_be_finished,
+ strfunction = None)
+
+Cat = Action(cat)
+f1_a = Command('f1-a.out', 'f1-a.in', cat)
+f1_b = Command('f1-b.out', 'f1-b.in', f1_a_out_must_be_finished)
+f2_a = Command('f2-a.out', 'f2-a.in', must_wait_for_f2_b_out)
+f2_b = Command('f2-b.out', 'f2-b.in', f2_a_out_must_not_be_finished)
+f3_a = Command('f3-a.out', 'f3-a.in', cat)
+f3_b = Command('f3-b.out', 'f3-b.in', f3_a_out_must_be_finished)
+Command('f1.out', f1_a + f1_b, cat)
+Command('f2.out', f2_a + f2_b, cat)
+Command('f3.out', f3_a + f3_b, cat)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('f1-a.in', "f1-a.in\n")
+test.write('f1-b.in', "f1-b.in\n")
+test.write('f2-a.in', "f2-a.in\n")
+test.write('f2-b.in', "f2-b.in\n")
+test.write('f3-a.in', "f3-a.in\n")
+test.write('f3-b.in', "f3-b.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build f1.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_match(test.workpath('f1.out'), "f1-a.in\nf1-b.in\n")
+
+
+
+scons.send("build -j2 f2.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('f2.out'), "f2-a.in\nf2-b.in\n")
+
+
+
+scons.send("build f3.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'))
+
+test.must_match(test.workpath('f3.out'), "f3-a.in\nf3-b.in\n")
+
+
+
+expect_stdout = """\
+scons>>> cat(["f1-a.out"], ["f1-a.in"])
+f1_a_out_must_be_finished(["f1-b.out"], ["f1-b.in"])
+cat(["f1.out"], ["f1-a.out", "f1-b.out"])
+scons>>> Touch("1")
+scons>>> must_wait_for_f2_b_out(["f2-a.out"], ["f2-a.in"])
+f2_a_out_must_not_be_finished(["f2-b.out"], ["f2-b.in"])
+cat(["f2.out"], ["f2-a.out", "f2-b.out"])
+scons>>> Touch("2")
+scons>>> cat(["f3-a.out"], ["f3-a.in"])
+f3_a_out_must_be_finished(["f3-b.out"], ["f3-b.in"])
+cat(["f3.out"], ["f3-a.out", "f3-b.out"])
+scons>>> Touch("3")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-k.py b/test/Interactive/option-k.py
new file mode 100644
index 00000000..f15605e9
--- /dev/null
+++ b/test/Interactive/option-k.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -k option, specified on the build command, causes
+us to keep going and build additional targets.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def error(target, source, env):
+ return 1
+e1 = Command('e1.out', 'e1.in', Action(error))
+f1 = Command('f1.out', 'f1.in', Copy('$TARGET', '$SOURCE'))
+Command('f2.out', 'f2.in', Copy('$TARGET', '$SOURCE'))
+Command('f3.out', 'f3.in', Copy('$TARGET', '$SOURCE'))
+Depends(f1, e1)
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+Command('3', [], Touch('$TARGET'))
+""")
+
+test.write('e1.in', "e1.in\n")
+test.write('e2.in', "e2.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive', combine=1)
+
+scons.send("build f1.out f2.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_not_exist(test.workpath('f2.out'))
+
+
+
+scons.send("build -k f1.out f2.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_match(test.workpath('f2.out'), "f2.in\n")
+
+
+
+scons.send("build f1.out f3.out\n")
+
+scons.send("build 3\n")
+
+test.wait_for(test.workpath('3'), popen=scons)
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_not_exist(test.workpath('f3.out'))
+
+
+
+expect_stdout = """\
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("1")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+Copy("f2.out", "f2.in")
+scons>>> Touch("2")
+scons>>> error(["e1.out"], ["e1.in"])
+scons: *** [e1.out] Error 1
+scons>>> Touch("3")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-n.py b/test/Interactive/option-n.py
new file mode 100644
index 00000000..f5ee1e3e
--- /dev/null
+++ b/test/Interactive/option-n.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the -n option, specified on the build command, reports
+what would be built but doesn't actually build anything.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build -n foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'), popen=scons)
+
+test.must_not_exist(test.workpath('foo.out'))
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'), popen=scons)
+
+test.must_match(test.workpath('foo.out'), "foo.in\n")
+
+
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/option-s.py b/test/Interactive/option-s.py
new file mode 100644
index 00000000..167d581f
--- /dev/null
+++ b/test/Interactive/option-s.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option
+to build a target, rebuild it when the input changes, and not rebuild
+it when the input doesn't change.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build -s foo.out 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> scons>>> scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/repeat-line.py b/test/Interactive/repeat-line.py
new file mode 100644
index 00000000..e85b9ea6
--- /dev/null
+++ b/test/Interactive/repeat-line.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that a blank line repeats the last (build) command.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+scons.send("\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> build foo.out 1
+Copy("foo.out", "foo.in")
+scons: `1' is up to date.
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>> build foo.out
+scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/shell.py b/test/Interactive/shell.py
new file mode 100644
index 00000000..9d5e8a2d
--- /dev/null
+++ b/test/Interactive/shell.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the ability of the "shell" command (and its "sh" and "!" aliases)
+to shell out of interactive mode.
+"""
+
+import string
+import sys
+
+import TestSCons
+
+test = TestSCons.TestSCons(combine=1)
+
+_python_ = TestSCons._python_
+
+shell_command_py = test.workpath('shell_command.py')
+_shell_command_py_ = '"%s"' % string.replace(shell_command_py, '\\', '\\\\')
+
+test.write(shell_command_py, """\
+print 'hello from shell_command.py'
+""")
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('!%(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('shell %(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('sh %(_python_)s %(_shell_command_py_)s\n' % locals())
+
+scons.send("\n")
+
+scons.send('!no_such_command arg1 arg2\n')
+
+scons.send("\n")
+
+scons.send("build foo.out\n")
+
+scons.send("\n")
+
+if sys.platform == 'win32':
+ no_such_error = 'The system cannot find the file specified'
+else:
+ no_such_error = 'No such file or directory'
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> hello from shell_command.py
+scons>>> !%(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> hello from shell_command.py
+scons>>> shell %(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> hello from shell_command.py
+scons>>> sh %(_python_)s %(_shell_command_py_)s
+hello from shell_command.py
+scons>>> scons: no_such_command: %(no_such_error)s
+scons>>> !no_such_command arg1 arg2
+scons: no_such_command: %(no_such_error)s
+scons>>> scons: `foo.out' is up to date.
+scons>>> build foo.out
+scons: `foo.out' is up to date.
+scons>>>
+""" % locals()
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/taskmastertrace.py b/test/Interactive/taskmastertrace.py
new file mode 100644
index 00000000..23b9ad90
--- /dev/null
+++ b/test/Interactive/taskmastertrace.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify use of the --taskmastertrace= option to the "build" command
+of --interactive mode.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build --taskmastertrace=- foo.out\n")
+
+scons.send("build 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> Taskmaster: 'foo.out': children:
+ ['foo.in']
+ waiting on unfinished children:
+ ['foo.in']
+Taskmaster: 'foo.in': evaluating foo.in
+Taskmaster: 'foo.out': children:
+ ['foo.in']
+ evaluating foo.out
+Copy("foo.out", "foo.in")
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/tree.py b/test/Interactive/tree.py
new file mode 100644
index 00000000..96e7d4ad
--- /dev/null
+++ b/test/Interactive/tree.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify basic operation of the --interactive command line option to build
+a target, rebuild it when the input changes, and not rebuild it when
+the input doesn't change.
+
+Also tests that "scons" can be used as a synonym for "build".
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+Command('2', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out\n")
+
+scons.send("build 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+test.write('foo.in', "foo.in 2\n")
+
+scons.send("build --tree=all foo.out\n")
+
+scons.send("scons 2\n")
+
+test.wait_for(test.workpath('2'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 2\n")
+
+
+
+scons.send("build --debug=tree foo.out\n")
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+scons>>> Touch("1")
+scons>>> Copy("foo.out", "foo.in")
++-foo.out
+ +-foo.in
+scons>>> Touch("2")
+scons>>> scons: `foo.out' is up to date.
++-foo.out
+ +-foo.in
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/unknown-command.py b/test/Interactive/unknown-command.py
new file mode 100644
index 00000000..2b773780
--- /dev/null
+++ b/test/Interactive/unknown-command.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the error message when an unknown command is typed in.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'))
+Command('1', [], Touch('$TARGET'))
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+
+
+scons = test.start(arguments = '-Q --interactive')
+
+scons.send("build foo.out 1\n")
+
+test.wait_for(test.workpath('1'))
+
+test.must_match(test.workpath('foo.out'), "foo.in 1\n")
+
+
+
+scons.send('this-is-an-unknown-command hello\n')
+
+expect_stdout = """\
+scons>>> Copy("foo.out", "foo.in")
+Touch("1")
+scons>>> *** Unknown command: this-is-an-unknown-command
+scons>>>
+"""
+
+test.finish(scons, stdout = expect_stdout)
+
+
+
+test.pass_test()
diff --git a/test/Interactive/version.py b/test/Interactive/version.py
new file mode 100644
index 00000000..84f70e85
--- /dev/null
+++ b/test/Interactive/version.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the behavior of the "version" subcommand.
+"""
+
+import TestCmd
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestCmd.match_re)
+
+test.write('SConstruct', "")
+
+
+
+# Construct the standard copyright marker so it doesn't get replaced
+# by the packaging build.
+copyright_marker = '__' + 'COPYRIGHT' + '__'
+
+copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007'
+
+fmt = '(%s|Copyright \\(c\\) %s The SCons Foundation)\n'
+
+copyright_line = fmt % (copyright_marker, copyright_years)
+
+
+
+expect1 = """\
+scons>>>
+scons>>>
+"""
+
+expect2 = """\
+scons>>>
+scons>>>
+"""
+
+test.run(arguments = '-Q --interactive',
+ stdin = "version\nexit\n")
+
+# Windows may or may not print a line for the script version
+# depending on whether it's invoked through scons.py or scons.bat.
+expect1 = r"""scons>>> SCons by Steven Knight et al\.:
+\tengine: v\S+, [^,]*, by \S+ on \S+
+%(copyright_line)sscons>>>
+""" % locals()
+
+expect2 = r"""scons>>> SCons by Steven Knight et al\.:
+\tscript: v\S+, [^,]*, by \S+ on \S+
+\tengine: v\S+, [^,]*, by \S+ on \S+
+%(copyright_line)sscons>>>
+""" % locals()
+
+stdout = test.stdout() + '\n'
+if not test.match_re(stdout, expect1) and not test.match_re(stdout, expect2):
+ print repr(stdout)
+ test.fail_test()
+
+
+
+test.pass_test()
diff --git a/test/Java/JAR.py b/test/Java/JAR.py
index f0951c62..ee552f45 100644
--- a/test/Java/JAR.py
+++ b/test/Java/JAR.py
@@ -120,21 +120,11 @@ test.run(arguments='classes.jar')
test.must_match('classes.jar',
'cvfm classes.jar foo.mf -C testdir bar.class\n')
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
- where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
- where_jar = test.where_is('jar')
-if not where_jar:
- test.skip_test("Could not find Java jar, skipping test(s).\n")
+
+
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
test.write("wrapper.py", """\
diff --git a/test/Java/JARCHDIR.py b/test/Java/JARCHDIR.py
index f7d9fca1..a3f2ec4f 100644
--- a/test/Java/JARCHDIR.py
+++ b/test/Java/JARCHDIR.py
@@ -40,21 +40,8 @@ import TestSCons
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
- where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
- where_jar = test.where_is('jar')
-if not where_jar:
- test.skip_test("Could not find Java jar, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
diff --git a/test/Java/JARFLAGS.py b/test/Java/JARFLAGS.py
index 3939d983..03a222b8 100644
--- a/test/Java/JARFLAGS.py
+++ b/test/Java/JARFLAGS.py
@@ -33,21 +33,10 @@ test = TestSCons.TestSCons()
test.subdir('src')
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
- where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
- where_jar = test.where_is('jar')
-if not where_jar:
- test.skip_test("Could not find Java jar, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
+
test.write('SConstruct', """
env = Environment(tools = ['javac', 'jar'],
@@ -75,9 +64,9 @@ public class Example1
expect = test.wrap_stdout("""\
%(where_javac)s -d classes -sourcepath src src/Example1\.java
-%(where_jar)s cvf test.jar classes/src/Example1\.class
+%(where_jar)s cvf test.jar -C classes src/Example1\.class
.*
-adding: classes/src/Example1\.class.*
+adding: src/Example1\.class.*
""" % locals())
expect = string.replace(expect, '/', os.sep)
diff --git a/test/Java/JAVABOOTCLASSPATH.py b/test/Java/JAVABOOTCLASSPATH.py
index 7723224f..5962b945 100644
--- a/test/Java/JAVABOOTCLASSPATH.py
+++ b/test/Java/JAVABOOTCLASSPATH.py
@@ -38,21 +38,8 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
test.write('SConstruct', """
env = Environment(tools = ['javac', 'javah'],
diff --git a/test/Java/JAVACFLAGS.py b/test/Java/JAVACFLAGS.py
index 045fb7b3..e2870549 100644
--- a/test/Java/JAVACFLAGS.py
+++ b/test/Java/JAVACFLAGS.py
@@ -31,14 +31,7 @@ import TestSCons
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
test.subdir('src')
diff --git a/test/Java/JAVACLASSPATH.py b/test/Java/JAVACLASSPATH.py
index 0ae7dea5..11f7c2c4 100644
--- a/test/Java/JAVACLASSPATH.py
+++ b/test/Java/JAVACLASSPATH.py
@@ -36,21 +36,8 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
test.write('SConstruct', """
env = Environment(tools = ['javac', 'javah'],
diff --git a/test/Java/JAVAH.py b/test/Java/JAVAH.py
index ecd3737f..95abd33f 100644
--- a/test/Java/JAVAH.py
+++ b/test/Java/JAVAH.py
@@ -24,9 +24,9 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import os
+import os.path
import string
-import sys
+
import TestSCons
_python_ = TestSCons._python_
@@ -95,21 +95,11 @@ line 3
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
+if java_version:
+ java_version = repr(java_version)
@@ -125,6 +115,9 @@ test.write('SConstruct', """
foo = Environment(tools = ['javac', 'javah', 'install'],
JAVAC = r'%(where_javac)s',
JAVAH = r'%(where_javah)s')
+jv = %(java_version)s
+if jv:
+ foo['JAVAVERSION'] = jv
javah = foo.Dictionary('JAVAH')
bar = foo.Clone(JAVAH = r'%(_python_)s wrapper.py ' + javah)
foo.Java(target = 'class1', source = 'com/sub/foo')
diff --git a/test/Java/JAVASOURCEPATH.py b/test/Java/JAVASOURCEPATH.py
index 069e2282..87e90add 100644
--- a/test/Java/JAVASOURCEPATH.py
+++ b/test/Java/JAVASOURCEPATH.py
@@ -36,14 +36,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
test.write('SConstruct', """
env = Environment(tools = ['javac', 'javah'],
diff --git a/test/Java/Java-1.4.py b/test/Java/Java-1.4.py
index 3d3d47ab..3c0e1ed8 100644
--- a/test/Java/Java-1.4.py
+++ b/test/Java/Java-1.4.py
@@ -38,14 +38,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.4')
@@ -234,10 +227,10 @@ public class NestedExample
{
public NestedExample()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
try {Thread.sleep(200);}
@@ -256,7 +249,7 @@ public class NestedExample
public static void main(String argv[])
{
- NestedExample e = new NestedExample();
+ new NestedExample();
}
}
""")
@@ -268,7 +261,7 @@ public class NestedExample
test.write(['src5', 'TestSCons.java'], """\
class TestSCons {
public static void main(String[] args) {
- Foo[] fooArray = new Foo[] { new Foo() };
+ new Foo();
}
}
@@ -355,12 +348,16 @@ def classes_must_match(dir, expect):
global failed
got = get_class_files(test.workpath(dir))
if expect != got:
- sys.stderr.write("Expected the following class files in '%s':\n" % dir)
- for c in expect:
- sys.stderr.write(' %s\n' % c)
- sys.stderr.write("Got the following class files in '%s':\n" % dir)
- for c in got:
- sys.stderr.write(' %s\n' % c)
+ missing = set(expect) - set(got)
+ if missing:
+ sys.stderr.write("Missing the following class files from '%s':\n" % dir)
+ for c in missing:
+ sys.stderr.write(' %s\n' % c)
+ unexpected = set(got) - set(expect)
+ if unexpected:
+ sys.stderr.write("Found the following unexpected class files in '%s':\n" % dir)
+ for c in unexpected:
+ sys.stderr.write(' %s\n' % c)
failed = 1
def classes_must_not_exist(dir, expect):
diff --git a/test/Java/Java-1.5.py b/test/Java/Java-1.5.py
index 4ac3d96c..f6d93c7a 100644
--- a/test/Java/Java-1.5.py
+++ b/test/Java/Java-1.5.py
@@ -38,15 +38,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-ENV['PATH'] = '/usr/lib/jvm/java-1.5.0-sun-1.5.0.11/bin' + os.pathsep + os.environ['PATH']
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.5')
@@ -235,10 +227,10 @@ public class NestedExample
{
public NestedExample()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
try {Thread.sleep(200);}
@@ -257,7 +249,7 @@ public class NestedExample
public static void main(String argv[])
{
- NestedExample e = new NestedExample();
+ new NestedExample();
}
}
""")
diff --git a/test/Java/Java-1.6.py b/test/Java/Java-1.6.py
index f2b629af..5bd8e2f5 100644
--- a/test/Java/Java-1.6.py
+++ b/test/Java/Java-1.6.py
@@ -38,15 +38,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-ENV['PATH'] = '/usr/lib/jvm/java-6-sun-1.6.0.00/bin' + os.pathsep + os.environ['PATH']
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac('1.6')
@@ -235,10 +227,10 @@ public class NestedExample
{
public NestedExample()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
- Thread t = new Thread() {
+ new Thread() {
public void start()
{
try {Thread.sleep(200);}
@@ -257,7 +249,7 @@ public class NestedExample
public static void main(String argv[])
{
- NestedExample e = new NestedExample();
+ new NestedExample();
}
}
""")
diff --git a/test/Java/RMIC.py b/test/Java/RMIC.py
index 2ab1804e..f9721c2f 100644
--- a/test/Java/RMIC.py
+++ b/test/Java/RMIC.py
@@ -92,21 +92,8 @@ line 3
test.fail_test(test.read(['outdir', 'test2.class']) != "test2.JAVA\nline 3\n")
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping non-simulated test(s).\n")
-
-if test.detect_tool('rmic', ENV=ENV):
- where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
-else:
- where_rmic = test.where_is('rmic')
-if not where_rmic:
- test.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_rmic = test.java_where_rmic()
test.write("wrapper.py", """\
import os
@@ -319,15 +306,20 @@ test.run(arguments = '.')
test.fail_test(test.read('wrapper.out') != "wrapper.py %s -d outdir2 -classpath class2 com.sub.bar.Example3 com.sub.bar.Example4\n" % where_rmic)
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Stub.class')))
-
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Stub.class')))
+test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Stub.class'))
+test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Stub.class'))
+test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Stub.class'))
+test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Stub.class'))
+
+# We used to check for _Skel.class files as well, but they're not
+# generated by default starting with Java 1.5, and they apparently
+# haven't been needed for a while. Don't bother looking, even if we're
+# running Java 1.4. If we think they're needed but they don't exist
+# the test.up_to_date() call below will detect it.
+#test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example1_Skel.class'))
+#test.must_exist(test.workpath('outdir1', 'com', 'sub', 'foo', 'Example2_Skel.class'))
+#test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example3_Skel.class'))
+#test.must_exist(test.workpath('outdir2', 'com', 'sub', 'bar', 'Example4_Skel.class'))
test.up_to_date(arguments = '.')
diff --git a/test/Java/RMICCOM.py b/test/Java/RMICCOM.py
index ed5e0d67..e8a56553 100644
--- a/test/Java/RMICCOM.py
+++ b/test/Java/RMICCOM.py
@@ -40,9 +40,9 @@ test.subdir('src')
-out_file1 = os.path.join('out', 'file1', 'class_Skel.class')
-out_file2 = os.path.join('out', 'file2', 'class_Skel.class')
-out_file3 = os.path.join('out', 'file3', 'class_Skel.class')
+out_file1 = os.path.join('out', 'file1', 'class_Stub.class')
+out_file2 = os.path.join('out', 'file2', 'class_Stub.class')
+out_file3 = os.path.join('out', 'file3', 'class_Stub.class')
diff --git a/test/Java/RMICCOMSTR.py b/test/Java/RMICCOMSTR.py
index 5a451ebd..a92bac53 100644
--- a/test/Java/RMICCOMSTR.py
+++ b/test/Java/RMICCOMSTR.py
@@ -41,9 +41,9 @@ test.subdir('src')
-out_file1 = os.path.join('out', 'file1', 'class_Skel.class')
-out_file2 = os.path.join('out', 'file2', 'class_Skel.class')
-out_file3 = os.path.join('out', 'file3', 'class_Skel.class')
+out_file1 = os.path.join('out', 'file1', 'class_Stub.class')
+out_file2 = os.path.join('out', 'file2', 'class_Stub.class')
+out_file3 = os.path.join('out', 'file3', 'class_Stub.class')
diff --git a/test/Java/multi-step.py b/test/Java/multi-step.py
index d185b4d5..9cac7597 100644
--- a/test/Java/multi-step.py
+++ b/test/Java/multi-step.py
@@ -33,22 +33,8 @@ import TestSCons
test = TestSCons.TestSCons()
-# This test requires javac and swig
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
swig = test.where_is('swig')
if not swig:
diff --git a/test/Java/no-JARCHDIR.py b/test/Java/no-JARCHDIR.py
new file mode 100644
index 00000000..795689c4
--- /dev/null
+++ b/test/Java/no-JARCHDIR.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify the Jar() behavior when we have no JARCHDIR set (it should
+automatically use the classdir that was deduced from the Java() call)
+and when we explicity set it to None (it should not use the Java()
+classdir attribute at all).
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+where_javac, java_version = test.java_where_javac()
+where_jar = test.java_where_jar()
+
+test.subdir('src')
+
+
+
+test.write(['src', 'a.java'], """\
+package foo.bar;
+public class a {}
+""")
+
+test.write(['src', 'b.java'], """\
+package foo.bar;
+public class b {}
+""")
+
+
+
+test.write('SConstruct', """\
+env = Environment(tools = ['javac', 'jar'],
+ JAVAC = r'%(where_javac)s',
+ JAR = r'%(where_jar)s')
+
+jar = env.Jar('x.jar', env.Java(target = 'classes', source = 'src'))
+""" % locals())
+
+test.run(arguments = '.')
+
+
+
+test.run(program = where_jar, arguments = 'tf x.jar')
+
+expect = """\
+foo/bar/a.class
+foo/bar/b.class
+"""
+
+if string.find(test.stdout(), expect) == -1:
+ print "Did not find expected string in standard output."
+ print "Expected =========================================================="
+ print expect
+ print "Output ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+
+
+test.run(arguments = '-c')
+
+
+
+test.write('SConstruct', """\
+env = Environment(tools = ['javac', 'jar'],
+ JAVAC = r'%(where_javac)s',
+ JAR = r'%(where_jar)s',
+ JARCHDIR = None)
+
+jar = env.Jar('x.jar', env.Java(target = 'classes', source = 'src'))
+""" % locals())
+
+test.run(arguments = '.')
+
+
+
+test.run(program = where_jar, arguments = 'tf x.jar')
+
+expect = """\
+classes/foo/bar/a.class
+classes/foo/bar/b.class
+"""
+
+if string.find(test.stdout(), expect) == -1:
+ print "Did not find expected string in standard output."
+ print "Expected =========================================================="
+ print expect
+ print "Output ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+
+
+test.pass_test()
diff --git a/test/Java/source-files.py b/test/Java/source-files.py
index 8d2506fb..63d1d92a 100644
--- a/test/Java/source-files.py
+++ b/test/Java/source-files.py
@@ -35,14 +35,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
test.write('SConstruct', """
diff --git a/test/Java/swig-dependencies.py b/test/Java/swig-dependencies.py
index 8df5e096..5477a2d4 100644
--- a/test/Java/swig-dependencies.py
+++ b/test/Java/swig-dependencies.py
@@ -34,29 +34,14 @@ import TestSCons
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
-
-if test.detect_tool('jar', ENV=ENV):
- where_jar = test.detect('JAR', 'jar', ENV=ENV)
-else:
- where_jar = test.where_is('jar')
-if not where_jar:
- test.skip_test("Could not find Java jar, skipping test(s).\n")
+swig = test.where_is('swig')
+if not swig:
+ test.skip_test('Can not find installed "swig", skipping test.\n')
+
+where_javac, java_version = test.java_where_javac()
+where_javah = test.java_where_javah()
+where_jar = test.java_where_jar()
test.subdir(['foo'],
['java'],
diff --git a/test/LIBPATH.py b/test/Libs/LIBPATH.py
index 7fd4f9bd..7fd4f9bd 100644
--- a/test/LIBPATH.py
+++ b/test/Libs/LIBPATH.py
diff --git a/test/LIBPREFIX.py b/test/Libs/LIBPREFIX.py
index a71e9673..a71e9673 100644
--- a/test/LIBPREFIX.py
+++ b/test/Libs/LIBPREFIX.py
diff --git a/test/LIBPREFIXES.py b/test/Libs/LIBPREFIXES.py
index 1e5d6c2b..1e5d6c2b 100644
--- a/test/LIBPREFIXES.py
+++ b/test/Libs/LIBPREFIXES.py
diff --git a/test/LIBS.py b/test/Libs/LIBS.py
index e9f65451..e9f65451 100644
--- a/test/LIBS.py
+++ b/test/Libs/LIBS.py
diff --git a/test/LIBSUFFIX.py b/test/Libs/LIBSUFFIX.py
index a71e9673..a71e9673 100644
--- a/test/LIBSUFFIX.py
+++ b/test/Libs/LIBSUFFIX.py
diff --git a/test/LIBSUFFIXES.py b/test/Libs/LIBSUFFIXES.py
index 29cbb18e..29cbb18e 100644
--- a/test/LIBSUFFIXES.py
+++ b/test/Libs/LIBSUFFIXES.py
diff --git a/test/Library.py b/test/Libs/Library.py
index 4bcb2c7d..4bcb2c7d 100644
--- a/test/Library.py
+++ b/test/Libs/Library.py
diff --git a/test/SHLIBPREFIX.py b/test/Libs/SHLIBPREFIX.py
index 87b92fae..87b92fae 100644
--- a/test/SHLIBPREFIX.py
+++ b/test/Libs/SHLIBPREFIX.py
diff --git a/test/SHLIBSUFFIX.py b/test/Libs/SHLIBSUFFIX.py
index dc88e3b1..dc88e3b1 100644
--- a/test/SHLIBSUFFIX.py
+++ b/test/Libs/SHLIBSUFFIX.py
diff --git a/test/SharedLibrary.py b/test/Libs/SharedLibrary.py
index f8447ca3..f8447ca3 100644
--- a/test/SharedLibrary.py
+++ b/test/Libs/SharedLibrary.py
diff --git a/test/Libs/SharedLibraryIxes.py b/test/Libs/SharedLibraryIxes.py
new file mode 100644
index 00000000..f9d14711
--- /dev/null
+++ b/test/Libs/SharedLibraryIxes.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test that we can build shared libraries and link against shared
+libraries that have non-standard library prefixes and suffixes.
+"""
+
+import re
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import sys
+isWindows = sys.platform == 'win32'
+
+env = Environment()
+
+# Make sure that the shared library can be located at runtime.
+env.Append(RPATH=['.'])
+env.Append(LIBPATH=['.'])
+
+# We first bake the LIBSUFFIXES, so that it will not change as a
+# side-effect of changing SHLIBSUFFIX.
+env['LIBSUFFIXES'] = map( env.subst, env.get('LIBSUFFIXES', []))
+
+weird_prefixes = ['libXX', 'libYY']
+
+if isWindows:
+ weird_suffixes = ['.xxx', '.yyy', '.xxx.dll', '.yyy.dll']
+ env.Append(CCFLAGS = '/MD')
+elif env['PLATFORM'] == 'darwin':
+ weird_suffixes = ['.xxx.dylib', '.yyy.dylib']
+else:
+ weird_suffixes = ['.xxx.so', '.yyy.so']
+
+shlibprefix = env.subst('$SHLIBPREFIX')
+shlibsuffix = env.subst('$SHLIBSUFFIX')
+
+progprefix = env.subst('$PROGPREFIX')
+progsuffix = env.subst('$PROGSUFFIX')
+
+goo_obj = env.SharedObject(source='goo.c')
+foo_obj = env.SharedObject(source='foo.c')
+prog_obj = env.SharedObject(source='prog.c')
+
+#
+# The following functions define all the different way that one can
+# use link againt a shared library.
+#
+def nodeInSrc(source, lib, libname):
+ return (source+lib, '')
+
+def pathInSrc(source, lib, libname):
+ return (source+map(str,lib), '')
+
+def nodeInLib(source, lib, libname):
+ return (source, lib)
+
+def pathInLib(source, lib, libname):
+ return (source, map(str,lib))
+
+def nameInLib(source, lib, libname):
+ # NOTE: libname must contain both the proper prefix and suffix.
+ #
+ # When using non-standard prefixes and suffixes, one has to
+ # provide the full name of the library since scons can not know
+ # which of the non-standard extension to use.
+ #
+ # Note that this is not necessarally SHLIBPREFIX and
+ # SHLIBSUFFIX. These are the ixes of the target library, not the
+ # ixes of the library that we are linking againt.
+ return (source, libname)
+
+libmethods = [
+ nodeInSrc, pathInSrc, nodeInLib, pathInLib,
+ nameInLib ]
+
+def buildAndlinkAgainst(builder, target, source, method, lib, libname, **kw):
+ '''Build a target using a given builder while linking againt a given
+ library using a specified method for linking against the library.'''
+
+ # On Windows, we have to link against the .lib file.
+ if isWindows:
+ for l in lib:
+ if str(l)[-4:] == '.lib':
+ lib = [l]
+ break
+ (source, LIBS) = method(source, lib, libname)
+ #build = builder(target=target, source=source, LIBS=LIBS, **kw)
+ kw = kw.copy()
+ kw['target'] = target
+ kw['source'] = source
+ kw['LIBS'] = LIBS
+ build = apply(builder, (), kw)
+
+ # Check that the build target depends on at least one of the
+ # library target.
+ found_dep = False
+ children = build[0].children()
+ for l in lib:
+ if l in children:
+ found_dep = True
+ break;
+ assert found_dep, \
+ "One of %s not found in %s, method=%s, libname=%s, shlibsuffix=%s" % \
+ (map(str,lib), map(str, build[0].children()), method.__name__, libname, shlibsuffix)
+ return build
+
+def prog(i,
+ goomethod, goolibprefix, goolibsuffix,
+ foomethod, foolibprefix, foolibsuffix):
+ '''Build a program
+
+ The program links against a shared library foo which itself links
+ against a shared library goo. The libraries foo and goo can use
+ arbitrary library prefixes and suffixes.'''
+
+ goo_name = goolibprefix+'goo'+str(i)+goolibsuffix
+ foo_name = foolibprefix+'foo'+str(i)+foolibsuffix
+ prog_name = progprefix+'prog'+str(i)+progsuffix
+
+ print 'Prog: %d, %s, %s, %s' % (i, goo_name, foo_name, prog_name)
+
+ # On Windows, we have to link against the .lib file.
+ if isWindows:
+ goo_libname = goolibprefix+'goo'+str(i)+'.lib'
+ foo_libname = foolibprefix+'foo'+str(i)+'.lib'
+ else:
+ goo_libname = goo_name
+ foo_libname = foo_name
+
+ goo_lib = env.SharedLibrary(
+ goo_name, goo_obj, SHLIBSUFFIX=goolibsuffix)
+ foo_lib = buildAndlinkAgainst(
+ env.SharedLibrary, foo_name, foo_obj,
+ goomethod, goo_lib, goo_libname, SHLIBSUFFIX=foolibsuffix)
+ prog = buildAndlinkAgainst(env.Program, prog_name, prog_obj,
+ foomethod, foo_lib, foo_libname)
+
+
+#
+# Create the list of all possible permutations to test.
+#
+i = 0
+tests = []
+prefixes = [shlibprefix] + weird_prefixes
+suffixes = [shlibsuffix] + weird_suffixes
+for foolibprefix in prefixes:
+ for foolibsuffix in suffixes:
+ for foomethod in libmethods:
+ for goolibprefix in prefixes:
+ for goolibsuffix in suffixes:
+ for goomethod in libmethods:
+ tests.append(
+ (i,
+ goomethod, goolibprefix, goolibsuffix,
+ foomethod, foolibprefix, foolibsuffix))
+ i = i + 1
+
+#
+# Pseudo-randomly choose 200 tests to run out of the possible
+# tests. (Testing every possible permutation would take too long.)
+#
+import random
+random.seed(123456)
+try:
+ random.shuffle(tests)
+except AttributeError:
+ pass
+
+for i in range(200):
+ apply(prog, tests[i])
+
+""")
+
+test.write('goo.c', r"""
+#include <stdio.h>
+
+#ifdef _WIN32
+#define EXPORT __declspec( dllexport )
+#else
+#define EXPORT
+#endif
+
+EXPORT void
+goo(void)
+{
+ printf("goo.c\n");
+}
+""")
+
+test.write('foo.c', r"""
+#include <stdio.h>
+
+#ifdef _WIN32
+#define EXPORT __declspec( dllexport )
+#else
+#define EXPORT
+#endif
+
+EXPORT void
+foo(void)
+{
+ goo();
+ printf("foo.c\n");
+}
+""")
+
+test.write('prog.c', r"""
+#include <stdio.h>
+
+void foo(void);
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ foo();
+ printf("prog.c\n");
+ return 0;
+}
+""")
+
+test.run(arguments = '.',
+ stderr=TestSCons.noisy_ar,
+ match=TestSCons.match_re_dotall)
+
+tests = re.findall(r'Prog: (\d+), (\S+), (\S+), (\S+)', test.stdout())
+expected = "goo.c\nfoo.c\nprog.c\n"
+
+for t in tests:
+ test.must_exist(t[1])
+ test.must_exist(t[2])
+ test.must_exist(t[3])
+ test.run(program = test.workpath(t[3]), stdout=expected)
+
+test.pass_test()
diff --git a/test/LoadableModule.py b/test/LoadableModule.py
index 5243fbf4..4a8e1eda 100644
--- a/test/LoadableModule.py
+++ b/test/LoadableModule.py
@@ -105,7 +105,8 @@ test.run(arguments = '.',
if string.find(sys.platform, 'darwin') != -1:
test.run(program='/usr/bin/file',
arguments = "foo1",
- stdout="foo1: Mach-O bundle ppc\n")
+ match = TestCmd.match_re,
+ stdout="foo1: Mach-O bundle (ppc|i386)\n")
if sys.platform in platforms_with_dlopen:
os.environ['LD_LIBRARY_PATH'] = test.workpath()
diff --git a/test/MSVC/pdb-manifest.py b/test/MSVC/pdb-manifest.py
new file mode 100644
index 00000000..00f3ee27
--- /dev/null
+++ b/test/MSVC/pdb-manifest.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that .pdb files work correctly in conjunction with manifest files.
+"""
+
+import sys
+
+import TestSCons
+
+_exe = TestSCons._exe
+_dll = TestSCons._dll
+_lib = TestSCons._lib
+
+test = TestSCons.TestSCons()
+
+if sys.platform != 'win32':
+ msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform
+ test.skip_test(msg)
+
+test.write('SConstruct', """\
+env = Environment()
+
+env['WINDOWS_INSERT_DEF'] = True
+env['WINDOWS_INSERT_MANIFEST'] = True
+env['PDB'] = '${TARGET.base}.pdb'
+env.Program('test', 'test.cpp')
+env.SharedLibrary('sharedlib', 'test.cpp')
+env.StaticLibrary('staticlib', 'test.cpp')
+""")
+
+test.write('test.cpp', """\
+#include <stdio.h>
+#include <stdlib.h>
+int
+main(int argc, char *argv)
+{
+ printf("test.cpp\\n");
+ exit (0);
+}
+""")
+
+test.write('sharedlib.def', """\
+""")
+
+test.run(arguments = '.')
+
+test.must_exist('test%s' % _exe)
+test.must_exist('test.pdb')
+
+test.must_exist('sharedlib%s' % _dll)
+test.must_exist('sharedlib.pdb')
+
+test.must_exist('staticlib%s' % _lib)
+test.must_not_exist('staticlib.pdb')
+
+test.pass_test()
diff --git a/test/QT/moc-from-header.py b/test/QT/moc-from-header.py
index 28781369..99364906 100644
--- a/test/QT/moc-from-header.py
+++ b/test/QT/moc-from-header.py
@@ -55,6 +55,8 @@ test.Qt_create_SConstruct('SConstruct')
test.write('SConscript', """\
Import("env")
env.Program(target = 'aaa', source = 'aaa.cpp')
+if env['PLATFORM'] == 'darwin':
+ env.Install('.', 'qt/lib/libmyqt.dylib')
""")
test.write('aaa.cpp', r"""
@@ -67,7 +69,7 @@ test.write('aaa.h', r"""
void aaa(void) Q_OBJECT;
""")
-test.run(arguments = aaa_exe)
+test.run()
test.up_to_date(options = '-n', arguments=aaa_exe)
diff --git a/test/QT/warnings.py b/test/QT/warnings.py
index baa1e6fa..ef94dd6f 100644
--- a/test/QT/warnings.py
+++ b/test/QT/warnings.py
@@ -59,9 +59,10 @@ match12 = r"""
scons: warning: Generated moc file 'aaa.moc' is not included by 'aaa.cpp'
""" + TestSCons.file_expr
-# In case 'ar' gives a warning about creating a library.
-test.fail_test(not test.match_re(test.stderr(), match12) and \
- not test.match_re(test.stderr(), match12 + ".+\n"))
+if not re.search(match12, test.stderr()):
+ print "Did not find expected regular expression in stderr:"
+ print test.stderr()
+ test.fail_test()
os.environ['QTDIR'] = test.QT
diff --git a/test/Repository/Java.py b/test/Repository/Java.py
index 6e8bbd6b..67ef605d 100644
--- a/test/Repository/Java.py
+++ b/test/Repository/Java.py
@@ -37,18 +37,8 @@ python = TestSCons.python
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
- test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
java = where_java
@@ -115,6 +105,8 @@ test.writable('repository', 0)
#
test.run(chdir = 'work1', options = opts, arguments = ".")
+os.environ['JAVA_HOME'] = '/usr/lib/jvm/java-1.5.0-sun-1.5.0.11'
+
test.run(program = java,
arguments = "-cp %s Foo1" % work1_classes,
stdout = "rep1/src/Foo1.java\n")
diff --git a/test/Repository/JavaH.py b/test/Repository/JavaH.py
index fc879041..258d9bd1 100644
--- a/test/Repository/JavaH.py
+++ b/test/Repository/JavaH.py
@@ -37,25 +37,9 @@ python = TestSCons.python
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('javah', ENV=ENV):
- where_javah = test.detect('JAVAH', 'javah', ENV=ENV)
-else:
- where_javah = test.where_is('javah')
-if not where_javah:
- test.skip_test("Could not find Java javah, skipping test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
- test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
+where_javah = test.java_where_javah()
java = where_java
javac = where_javac
diff --git a/test/Repository/LIBPATH.py b/test/Repository/LIBPATH.py
index 9216a8b3..0f80acef 100644
--- a/test/Repository/LIBPATH.py
+++ b/test/Repository/LIBPATH.py
@@ -59,6 +59,13 @@ def write_LIBDIRFLAGS(env, target, source):
return 0
env_zzz.Command('zzz.out', aaa_exe, write_LIBDIRFLAGS)
env_yyy.Command('yyy.out', bbb_exe, write_LIBDIRFLAGS)
+
+if env_yyy['PLATFORM'] == 'darwin':
+ # The Mac OS X linker complains about nonexistent directories
+ # specified as -L arguments. Suppress its warnings so we don't
+ # treat the warnings on stderr as a failure.
+ env_yyy.Append(LINKFLAGS=['-w'])
+ env_zzz.Append(LINKFLAGS=['-w'])
""")
test.write(['work', 'aaa.c'], r"""
diff --git a/test/Repository/RMIC.py b/test/Repository/RMIC.py
index ebe6a83d..bf8edffd 100644
--- a/test/Repository/RMIC.py
+++ b/test/Repository/RMIC.py
@@ -28,34 +28,17 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Test building Java applications when using Repositories.
"""
-import os
import string
-import sys
+
import TestSCons
python = TestSCons.python
test = TestSCons.TestSCons()
-ENV = test.java_ENV()
-
-if test.detect_tool('javac', ENV=ENV):
- where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
-else:
- where_javac = test.where_is('javac')
-if not where_javac:
- test.skip_test("Could not find Java javac, skipping test(s).\n")
-
-if test.detect_tool('rmic', ENV=ENV):
- where_rmic = test.detect('RMIC', 'rmic', ENV=ENV)
-else:
- where_rmic = test.where_is('rmic')
-if not where_rmic:
- test.skip_test("Could not find Java rmic, skipping non-simulated test(s).\n")
-
-where_java = test.where_is('java')
-if not where_java:
- test.skip_test("Could not find Java java, skipping test(s).\n")
+where_javac, java_version = test.java_where_javac()
+where_java = test.java_where_java()
+where_rmic = test.java_where_rmic()
java = where_java
javac = where_javac
@@ -185,15 +168,20 @@ test.run(chdir = 'work1', options = opts, arguments = ".")
# see that they were built from the proper rep1 sources, but I don't
# know how to do that with RMI, so punt for now.
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+# We used to check for _Skel.class files as well, but they're not
+# generated by default starting with Java 1.5, and they apparently
+# haven't been needed for a while. Don't bother looking, even if we're
+# running Java 1.4. If we think they're needed but they don't exist
+# the variou test.up_to_date() calls below will detect it.
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
+#test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
test.up_to_date(chdir = 'work1', options = opts, arguments = ".")
@@ -294,15 +282,15 @@ test.fail_test(string.find(test.stdout(), ' com.sub.foo.Foo1 com.sub.foo.Foo2')
# see that they were built from the proper work1 sources, but I don't
# know how to do that with RMI, so punt for now.
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_not_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
+#test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist (test.workpath('work1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
test.up_to_date(chdir = 'work1', options = opts, arguments = ".")
@@ -315,10 +303,11 @@ test.run(chdir = 'rep1', options = opts, arguments = ".")
# see that they were built from the proper work1 sources, but I don't
# know how to do that with RMI, so punt for now.
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+#test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist(test.workpath('rep1', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
test.up_to_date(chdir = 'rep1', options = opts, arguments = ".")
@@ -343,13 +332,16 @@ Local(rmi_classes)
test.run(chdir = 'work3', options = opts, arguments = ".")
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Hello.class')))
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo1.class')))
-test.fail_test(os.path.exists(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo2.class')))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Hello.class'))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo1.class'))
+test.must_not_exist(test.workpath('work3', 'classes', 'com', 'sub', 'foo', 'Foo2.class'))
+
+test.must_exist (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class'))
+test.must_exist (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class'))
+
+#test.must_exist (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class'))
+#test.must_exist (test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class'))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo1_Stub.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Skel.class')))
-test.fail_test(not os.path.exists(test.workpath('work3', 'outdir', 'com', 'sub', 'foo', 'Foo2_Stub.class')))
+test.up_to_date(chdir = 'work3', options = opts, arguments = ".")
test.pass_test()
diff --git a/test/SWIG/module-parens.py b/test/SWIG/module-parens.py
new file mode 100644
index 00000000..0d89ebe9
--- /dev/null
+++ b/test/SWIG/module-parens.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that we handle %module(directors="1") statements, both with and
+without white space before the opening parenthesis.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+swig = test.where_is('swig')
+
+if not swig:
+ test.skip_test('Can not find installed "swig", skipping test.\n')
+
+python_include_dir = test.get_python_inc()
+
+test.write(['SConstruct'], """\
+env = Environment(SWIGFLAGS = '-python',
+ CPPPATH=r"%(python_include_dir)s")
+
+import sys
+if sys.version[0] == '1':
+ # SWIG requires the -classic flag on pre-2.0 Python versions.
+ env.Append(SWIGFLAGS = ' -classic')
+
+env.SharedLibrary('test1.so', 'test1.i')
+env.SharedLibrary('test2.so', 'test2.i')
+""" % locals())
+
+test.write(['test1.cc'], """\
+int test1func()
+{
+ return 0;
+}
+""")
+
+test.write(['test1.h'], """\
+int test1func();
+""")
+
+test.write(['test1.i'], """\
+%module(directors="1") test1
+
+%{
+#include "test1.h"
+%}
+
+%include "test1.h"
+""")
+
+test.write(['test2.cc'], """\
+int test2func()
+{
+ return 0;
+}
+""")
+
+test.write(['test2.h'], """\
+int test2func();
+""")
+
+test.write(['test2.i'], """\
+%module (directors="1") test2
+
+%{
+#include "test2.h"
+%}
+
+%include "test2.h"
+""")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
diff --git a/test/Scanner/generated.py b/test/Scanner/generated.py
index dd2c938c..ef91189f 100644
--- a/test/Scanner/generated.py
+++ b/test/Scanner/generated.py
@@ -318,10 +318,10 @@ def write_out(file, dict):
f.write(file + ": " + str(dict[k]) + "\\n")
f.close()
-orig_function = CScan.scan
+orig_function = CScan.__call__
-def MyCScan(node, paths, orig_function=orig_function):
- deps = orig_function(node, paths)
+def MyCScan(node, paths, cwd, orig_function=orig_function):
+ deps = orig_function(node, paths, cwd)
global Scanned
n = str(node)
@@ -333,7 +333,7 @@ def MyCScan(node, paths, orig_function=orig_function):
return deps
-CScan.scan = MyCScan
+CScan.__call__ = MyCScan
env = Environment(CPPPATH = ".")
l = env.StaticLibrary("g", Split("libg_1.c libg_2.c libg_3.c"))
diff --git a/test/TEX/build_dir_dup0.py b/test/TEX/build_dir_dup0.py
new file mode 100644
index 00000000..8035957b
--- /dev/null
+++ b/test/TEX/build_dir_dup0.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Test creation of a fully-featured TeX document (with bibliography
+and index) in a build_dir.
+
+Test courtesy Rob Managan.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+latex = test.where_is('latex')
+dvipdf = test.where_is('dvipdf')
+makeindex = test.where_is('makeindex')
+bibtex = test.where_is('bibtex')
+if not latex or not makeindex or not bibtex or not dvipdf:
+ test.skip_test("Could not find 'latex', 'makeindex', 'bibtex', or dvipdf; skipping test.\n")
+
+test.subdir(['docs'])
+
+
+test.write(['SConstruct'], """\
+import os
+
+env = Environment(ENV = { 'PATH' : os.environ['PATH'] },
+ TOOLS = ['tex', 'latex', 'dvipdf'])
+Export(['env'])
+
+SConscript(os.path.join('docs', 'SConscript'),
+ build_dir=os.path.join('mybuild','docs'),
+ duplicate=0)
+""")
+
+
+test.write(['docs', 'SConscript'], """\
+Import('env')
+
+test_dvi = env.DVI(source='test.tex')
+testpdf = env.PDF(source=test_dvi)
+""")
+
+
+test.write(['docs', 'Fig1.ps'], """\
+%!PS-Adobe-2.0 EPSF-2.0
+%%Title: Fig1.fig
+%%Creator: fig2dev Version 3.2 Patchlevel 4
+%%CreationDate: Tue Apr 25 09:56:11 2006
+%%BoundingBox: 0 0 98 98
+%%Magnification: 1.0000
+%%EndComments
+/$F2psDict 200 dict def
+$F2psDict begin
+$F2psDict /mtrx matrix put
+/col-1 {0 setgray} bind def
+/col0 {0.000 0.000 0.000 srgb} bind def
+
+end
+save
+newpath 0 98 moveto 0 0 lineto 98 0 lineto 98 98 lineto closepath clip newpath
+-24.9 108.2 translate
+1 -1 scale
+
+/cp {closepath} bind def
+/ef {eofill} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/sc {scale} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/tr {translate} bind def
+ /DrawEllipse {
+ /endangle exch def
+ /startangle exch def
+ /yrad exch def
+ /xrad exch def
+ /y exch def
+ /x exch def
+ /savematrix mtrx currentmatrix def
+ x y tr xrad yrad sc 0 0 1 startangle endangle arc
+ closepath
+ savematrix setmatrix
+ } def
+
+/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def
+/$F2psEnd {$F2psEnteredState restore end} def
+
+$F2psBegin
+10 setmiterlimit
+ 0.06299 0.06299 sc
+%
+% Fig objects follow
+%
+7.500 slw
+% Ellipse
+n 1170 945 766 766 0 360 DrawEllipse gs col0 s gr
+
+$F2psEnd
+rs
+""")
+
+
+test.write(['docs', 'Fig1.tex'],
+r"""\begin{picture}(0,0)%
+\includegraphics{Fig1.ps}%
+\end{picture}%
+\setlength{\unitlength}{4144sp}%
+%
+\begingroup\makeatletter\ifx\SetFigFont\undefined%
+\gdef\SetFigFont#1#2#3#4#5{%
+ \reset@font\fontsize{#1}{#2pt}%
+ \fontfamily{#3}\fontseries{#4}\fontshape{#5}%
+ \selectfont}%
+\fi\endgroup%
+\begin{picture}(1548,1546)(397,-879)
+\put(856,-196){\makebox(0,0)[lb]{\smash{\SetFigFont{12}{14.4}{\rmdefault}{\mddefault}{\updefault}{\color[rgb]{0,0,0}center $r_0$}%
+}}}
+\end{picture}
+""")
+
+
+test.write(['docs', 'test.bib'], """\
+%% This BibTeX bibliography file was created using BibDesk.
+%% http://bibdesk.sourceforge.net/
+
+%% Saved with string encoding Western (ASCII)
+
+@techreport{AnAuthor:2006fk,
+ Author = {A. N. Author},
+ Date-Added = {2006-11-15 12:51:30 -0800},
+ Date-Modified = {2006-11-15 12:52:35 -0800},
+ Institution = {none},
+ Month = {November},
+ Title = {A Test Paper},
+ Year = {2006}}
+""")
+
+
+test.write(['docs', 'test.tex'],
+r"""\documentclass{report}
+
+\usepackage{graphicx}
+\usepackage{epsfig,color} % for .tex version of figures if we go that way
+
+\usepackage{makeidx}
+\makeindex
+
+\begin{document}
+
+\title{Report Title}
+
+\author{A. N. Author}
+
+\maketitle
+
+\begin{abstract}
+there is no abstract
+\end{abstract}
+
+\tableofcontents
+\listoffigures
+
+\chapter{Introduction}
+
+The introduction is short.
+
+\index{Acknowledgements}
+
+\section{Acknowledgements}
+
+The Acknowledgements are show as well \cite{AnAuthor:2006fk}.
+
+\index{Getting the Report}
+
+To get a hard copy of this report call me.
+
+\begin{figure}[htbp]
+\begin{center}
+\input{Fig1.tex} % testing figure variant that uses TeX labeling
+\caption{Zone and Node indexing}
+\label{fig1}
+\end{center}
+\end{figure}
+
+All done now.
+
+\bibliographystyle{unsrt}
+\bibliography{test}
+\newpage
+
+\printindex
+
+\end{document}
+""")
+
+
+# makeindex will write status messages to stderr (grrr...), so ignore it.
+test.run(arguments = '.', stderr=None)
+
+
+# All (?) the files we expect will get created in the build_dir
+# (mybuild/docs) and not in the srcdir (docs).
+files = [
+ 'test.aux',
+ 'test.bbl',
+ 'test.blg',
+ 'test.dvi',
+ 'test.idx',
+ 'test.ilg',
+ 'test.ind',
+ 'test.lof',
+ 'test.log',
+ 'test.pdf',
+ 'test.toc',
+]
+
+for f in files:
+ test.must_exist(['mybuild', 'docs', f])
+ test.must_not_exist(['docs', f])
+
+
+test.pass_test()
diff --git a/test/TEX/multi-run.py b/test/TEX/multi-run.py
index d4e2d798..686263d8 100644
--- a/test/TEX/multi-run.py
+++ b/test/TEX/multi-run.py
@@ -28,6 +28,8 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Validate that both .tex and .ltx files can handle a LaTeX-style
bibliography (by calling $BIBTEX to generate a .bbl file) and
correctly re-run to resolve undefined references.
+
+Also verifies that package warnings are caught and re-run as needed.
"""
import string
@@ -42,7 +44,7 @@ latex = test.where_is('latex')
if not tex and not latex:
test.skip_test("Could not find tex or latex; skipping test(s).\n")
-test.subdir('work1', 'work2', 'work4')
+test.subdir('work1', 'work2', 'work3', 'work4')
input_file = r"""
@@ -64,6 +66,62 @@ Hello world.
\end{document}
"""
+input_file3 = r"""
+\documentclass{article}
+\usepackage{longtable}
+
+\begin{document}
+As stated in the last paper, this is a bug-a-boo.
+here is some more junk and another table
+here is some more junk and another table
+
+\begin{longtable}[l]{rlll}
+ Isotope &\multicolumn{1}{c}{Abar} &Name\\
+\\
+ 1001 &1.0078 &Proton &$p$\\
+ 1002 &2.0141 &Deuterium &$d$\\
+ 1003 &3.0170 &Tritium &$t$\\
+ 2003 &3.0160 &Helium 3 &He$^3$\\
+ 2004 &4.0026 &Helium 4 &He$^{4}$\\
+\end{longtable}
+
+and a closing comment
+
+ These parameters and arrays are filled in when the parameter \textbf{iftnrates}
+ is set to 1:
+
+\begin{longtable}[l]{ll}
+\\
+\textbf{nxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{nxxxxx} &Total number of particles made by xxxxx reaction\\
+\textbf{nxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{nxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{nxxxxx} &Total number of particles made by xxxxx reaction\\
+\textbf{nxxxx} &Total number of particles made by xxxx reaction\\
+\textbf{pxxxx} &Total number of particles made by xxxx reaction\\
+\\
+\textbf{rnxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxxx} &Regional total of particles made by xxxxx reaction\\
+\textbf{rnxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rnxxxxx} &Regional total of particles made by xxxxx reaction\\
+\textbf{rnxxxx} &Regional total of particles made by xxxx reaction\\
+\textbf{rpxxxx} &Regional total of particles made by xxxx reaction\\
+\\
+\textbf{reactot}(r) &Total number of reactions for reaction r\\
+\textbf{reacreg}(r,ir) &Total number of reactions for reaction r in region ir\\
+\end{longtable}
+
+
+\end{document}
+"""
+
bibfile = r"""
@Article{X,
author = "Mr. X",
@@ -95,6 +153,20 @@ PDF( "foo.tex" )
print foo_log
test.fail_test(1)
+ test.write(['work3', 'SConstruct'], """\
+DVI( "foo3.tex" )
+""")
+
+ test.write(['work3', 'foo3.tex'], input_file3)
+
+ test.run(chdir = 'work3', arguments = '.')
+
+ foo_log = test.read(['work3', 'foo3.log'])
+ if string.find(foo_log, 'Rerun LaTeX') != -1:
+ print 'foo.log contains "Rerun LaTeX":'
+ print foo_log
+ test.fail_test(1)
+
if latex:
@@ -117,6 +189,20 @@ PDF( "foo.ltx" )
print foo_log
test.fail_test(1)
+ test.write(['work3', 'SConstruct'], """\
+DVI( "foo3.tex" )
+PDF( "foo3.tex" )
+""")
+
+ test.write(['work3', 'foo3.tex'], input_file3)
+
+ test.run(chdir = 'work3', arguments = '.')
+
+ foo_log = test.read(['work3', 'foo3.log'])
+ if string.find(foo_log, 'Rerun LaTeX') != -1:
+ print 'foo.log contains "Rerun LaTeX":'
+ print foo_log
+ test.fail_test(1)
test.write(['work4', 'SConstruct'], """\
diff --git a/test/YACC/live.py b/test/YACC/live.py
index 4934570d..0e5f1562 100644
--- a/test/YACC/live.py
+++ b/test/YACC/live.py
@@ -103,13 +103,19 @@ graph: GRAPH_T
%%
""")
+import sys
+if sys.platform == 'darwin':
+ file_hpp = 'file.cpp.h'
+else:
+ file_hpp = 'file.hpp'
+
test.write("hello.cpp", """\
-#include "file.hpp"
+#include "%(file_hpp)s"
int main()
{
}
-""")
+""" % locals())
test.write('foo.y', yacc % 'foo.y')
diff --git a/test/build-errors.py b/test/build-errors.py
deleted file mode 100644
index e25fbd50..00000000
--- a/test/build-errors.py
+++ /dev/null
@@ -1,223 +0,0 @@
-#!/usr/bin/env python
-#
-# __COPYRIGHT__
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-
-import os
-import string
-import sys
-import TestCmd
-import TestSCons
-
-test = TestSCons.TestSCons()
-
-no_such_file = test.workpath("no_such_file")
-not_executable = test.workpath("not_executable")
-
-test.write(not_executable, "\n")
-
-test.write("f1.in", "\n")
-test.write("f2.in", "\n")
-test.write("f3.in", "\n")
-
-test.write('SConstruct1', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld' : bld })
-env.bld(target = 'f1', source = 'f1.in')
-""" % string.replace(no_such_file, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct1 .',
- stdout = test.wrap_stdout("%s f1.in f1\n" % no_such_file, error=1),
- stderr = None,
- status = 2)
-
-bad_command = """\
-Bad command or file name
-"""
-
-unrecognized = """\
-'%s' is not recognized as an internal or external command,
-operable program or batch file.
-scons: *** [%s] Error 1
-"""
-
-unspecified = """\
-The name specified is not recognized as an
-internal or external command, operable program or batch file.
-scons: *** [%s] Error 1
-"""
-
-not_found_1 = """
-sh: %s: not found
-scons: *** [%s] Error 1
-"""
-
-not_found_2 = """
-sh: %s: not found
-scons: *** [%s] Error 1
-"""
-
-No_such = """\
-%s: No such file or directory
-scons: *** [%s] Error 127
-"""
-
-cannot_execute = """\
-%s: cannot execute
-scons *** [%s] Error 126
-"""
-
-Permission_denied = """\
-%s: Permission denied
-scons: *** [%s] Error 126
-"""
-
-permission_denied = """\
-%s: permission denied
-scons: *** [%s] Error 126
-"""
-
-is_a_directory = """\
-%s: is a directory
-scons: *** [%s] Error 126
-"""
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
- errs = [
- bad_command,
- unrecognized % (no_such_file, 'f1'),
- unspecified % 'f1'
- ]
- test.fail_test(not test.stderr() in errs)
-else:
- errs = [
- not_found_1 % (no_such_file, 'f1'),
- not_found_2 % (no_such_file, 'f1'),
- No_such % (no_such_file, 'f1'),
- ]
- error_message_not_found = 1
- for err in errs:
- if string.find(test.stderr(), err) != -1:
- error_message_not_found = None
- break
- test.fail_test(error_message_not_found)
-
-test.write('SConstruct2', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld': bld })
-env.bld(target = 'f2', source = 'f2.in')
-""" % string.replace(not_executable, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct2 .',
- stdout = test.wrap_stdout("%s f2.in f2\n" % not_executable, error=1),
- stderr = None,
- status = 2)
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
- errs = [
- bad_command,
- unrecognized % (not_executable, 'f2'),
- unspecified % 'f2'
- ]
- test.fail_test(not test.stderr() in errs)
-else:
- errs = [
- cannot_execute % (not_executable, 'f2'),
- Permission_denied % (not_executable, 'f2'),
- permission_denied % (not_executable, 'f2'),
- ]
- error_message_not_found = 1
- for err in errs:
- if string.find(test.stderr(), err) != -1:
- error_message_not_found = None
- break
- test.fail_test(error_message_not_found)
-
-test.write('SConstruct3', r"""
-bld = Builder(action = '%s $SOURCES $TARGET')
-env = Environment(BUILDERS = { 'bld' : bld })
-env.bld(target = 'f3', source = 'f3.in')
-""" % string.replace(test.workdir, '\\', '\\\\'))
-
-test.run(arguments='-f SConstruct3 .',
- stdout = test.wrap_stdout("%s f3.in f3\n" % test.workdir, error=1),
- stderr = None,
- status = 2)
-
-test.description_set("Incorrect STDERR:\n%s\n" % test.stderr())
-if os.name == 'nt':
- errs = [
- bad_command,
- unrecognized % (test.workdir, 'f3'),
- unspecified % 'f3'
- ]
- test.fail_test(not test.stderr() in errs)
-else:
- errs = [
- cannot_execute % (not_executable, 'f3'),
- is_a_directory % (test.workdir, 'f3'),
- ]
- error_message_not_found = 1
- for err in errs:
- if string.find(test.stderr(), err) != -1:
- error_message_not_found = None
- break
- test.fail_test(error_message_not_found)
-
-test.write('SConstruct4', r"""
-env = Environment()
-env.Command('test.out', 'test.in', Copy('$TARGET', '$SOURCE'))
-env.InstallAs('test2.out', 'test.out')
-# Mark test2.out as precious so we'll handle the exception in
-# FunctionAction() rather than when the target is cleaned before building.
-env.Precious('test2.out')
-env.Default('test2.out')
-""")
-
-test.write('test.in', "test.in 1\n")
-
-test.run(arguments = '-f SConstruct4 .')
-
-test.write('test.in', "test.in 2\n")
-
-test.writable('test2.out', 0)
-f = open(test.workpath('test2.out'))
-
-test.run(arguments = '-f SConstruct4 .',
- stderr = None,
- status = 2)
-
-f.close()
-test.writable('test2.out', 1)
-
-test.description_set("Incorrect STDERR:\n%s" % test.stderr())
-errs = [
- "scons: *** [test2.out] test2.out: Permission denied\n",
- "scons: *** [test2.out] test2.out: permission denied\n",
-]
-test.fail_test(test.stderr() not in errs)
-
-test.pass_test()
diff --git a/test/builderrors.py b/test/builderrors.py
index 8c7c4ab7..28c9a0ae 100644
--- a/test/builderrors.py
+++ b/test/builderrors.py
@@ -145,10 +145,10 @@ test.fail_test(string.find(err, 'Exception') != -1 or \
# Test bad shell ('./one' is a dir, so it can't be used as a shell).
# This will also give an exit status not in exitvalmap,
-# with error "Permission denied".
+# with error "Permission denied" or "No such file or directory".
test.write('SConstruct', """
env=Environment()
-if env['PLATFORM'] == 'posix':
+if env['PLATFORM'] in ('posix', 'darwin'):
from SCons.Platform.posix import fork_spawn
env['SPAWN'] = fork_spawn
env['SHELL'] = 'one'
@@ -157,10 +157,14 @@ env.Command(target='badshell.out', source=[], action='foo')
test.run(status=2, stderr=None)
err = test.stderr()
-test.fail_test(string.find(err, 'Exception') != -1 or \
- string.find(err, 'Traceback') != -1)
-test.fail_test(string.find(err, "ermission") == -1 and \
- string.find(err, "such file") == -1)
+if string.find(err, 'Exception') != -1 or string.find(err, 'Traceback') != -1:
+ print "Exception or Traceback found in the following error output:"
+ print err
+ test.fail_test()
+if string.find(err, 'ermission') == -1 and string.find(err, 'such file') == -1:
+ print "Missing '[Pp]ermission' or 'such file' string in the following error output:"
+ print err
+ test.fail_test()
# Test command with exit status -1.
diff --git a/test/import.py b/test/import.py
index 9c5d3afb..85948f9a 100644
--- a/test/import.py
+++ b/test/import.py
@@ -96,6 +96,7 @@ tools = [
'g77',
'gas',
'gcc',
+ 'gfortran',
'gnulink',
'gs',
'hpc++',
diff --git a/test/option-k.py b/test/option-k.py
index 0a46606a..4a460a2b 100644
--- a/test/option-k.py
+++ b/test/option-k.py
@@ -32,7 +32,7 @@ _python_ = TestSCons._python_
test = TestSCons.TestSCons()
-test.subdir('work1', 'work2')
+test.subdir('work1', 'work2', 'work3')
@@ -49,6 +49,11 @@ import sys
sys.exit(1)
""")
+
+#
+# Test: work1
+#
+
test.write(['work1', 'SConstruct'], """\
Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
Fail = Builder(action = r'%(_python_)s ../fail.py $TARGETS')
@@ -90,7 +95,27 @@ test.must_not_exist(test.workpath('work1', 'aaa.1'))
test.must_not_exist(test.workpath('work1', 'aaa.out'))
test.must_match(['work1', 'bbb.out'], "succeed.py: bbb.out\n")
+expect = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Cleaning targets ...
+Removed bbb.out
+scons: done cleaning targets.
+"""
+
+test.run(chdir = 'work1',
+ arguments = '--clean --keep-going aaa.out bbb.out',
+ stdout = expect)
+
+test.must_not_exist(test.workpath('work1', 'aaa.1'))
+test.must_not_exist(test.workpath('work1', 'aaa.out'))
+test.must_not_exist(test.workpath('work1', 'bbb.out'))
+
+
+#
+# Test: work2
+#
test.write(['work2', 'SConstruct'], """\
Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
@@ -126,4 +151,145 @@ test.must_match(['work2', 'ddd.out'], "succeed.py: ddd.out\n")
+#
+# Test: work3
+#
+# Check that the -k (keep-going) switch works correctly when the Nodes
+# forms a DAG. The test case is the following
+#
+# all
+# |
+# +-----+-----+-------------+
+# | | |
+# a1 a2 a3
+# | | |
+# + +---+---+ +---+---+
+# \ | / | |
+# \ bbb.out / a4 ccc.out
+# \ / /
+# \ / /
+# \ / /
+# aaa.out (fails)
+#
+
+test.write(['work3', 'SConstruct'], """\
+Succeed = Builder(action = r'%(_python_)s ../succeed.py $TARGETS')
+Fail = Builder(action = r'%(_python_)s ../fail.py $TARGETS')
+env = Environment(BUILDERS = { 'Succeed' : Succeed, 'Fail' : Fail })
+a = env.Fail('aaa.out', 'aaa.in')
+b = env.Succeed('bbb.out', 'bbb.in')
+c = env.Succeed('ccc.out', 'ccc.in')
+
+a1 = Alias( 'a1', a )
+a2 = Alias( 'a2', a+b)
+a4 = Alias( 'a4', c)
+a3 = Alias( 'a3', a4+c)
+
+Alias('all', a1+a2+a3)
+""" % locals())
+
+test.write(['work3', 'aaa.in'], "aaa.in\n")
+test.write(['work3', 'bbb.in'], "bbb.in\n")
+test.write(['work3', 'ccc.in'], "ccc.in\n")
+
+
+# Test tegular build (i.e. without -k)
+test.run(chdir = 'work3',
+ arguments = '.',
+ status = 2,
+ stderr = None,
+ stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+scons: building terminated because of errors.
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+ arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Current directory
+test.run(chdir = 'work3',
+ arguments = '-k .',
+ status = 2,
+ stderr = None,
+ stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+ arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Single target
+test.run(chdir = 'work3',
+ arguments = '--keep-going all',
+ status = 2,
+ stderr = None,
+ stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
+test.run(chdir = 'work3',
+ arguments = '-c .')
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_not_exist(['work3', 'bbb.out'])
+test.must_not_exist(['work3', 'ccc.out'])
+
+
+# Separate top-level targets
+test.run(chdir = 'work3',
+ arguments = '-k a1 a2 a3',
+ status = 2,
+ stderr = None,
+ stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+%(_python_)s ../fail.py aaa.out
+%(_python_)s ../succeed.py bbb.out
+%(_python_)s ../succeed.py ccc.out
+scons: done building targets (errors occurred during build).
+""" % locals())
+
+test.must_not_exist(['work3', 'aaa.out'])
+test.must_exist(['work3', 'bbb.out'])
+test.must_exist(['work3', 'ccc.out'])
+
+
test.pass_test()
diff --git a/test/option/debug-includes.py b/test/option/debug-includes.py
index 172cbb08..70857e66 100644
--- a/test/option/debug-includes.py
+++ b/test/option/debug-includes.py
@@ -79,7 +79,14 @@ includes = """
+-bar.h
"""
test.run(arguments = "--debug=includes foo.ooo")
-test.fail_test(string.find(test.stdout(), includes) == -1)
+
+if string.find(test.stdout(), includes) == -1:
+ print "Did not find expected string in standard output."
+ print "Expected =========================================================="
+ print includes
+ print "Actual ============================================================"
+ print test.stdout()
+ test.fail_test()
# In an ideal world, --debug=includes would also work when there's a build
# failure, but this would require even more complicated logic to scan
diff --git a/test/option/debug-memoizer.py b/test/option/debug-memoizer.py
index 8c8b2cf2..ad35b5d5 100644
--- a/test/option/debug-memoizer.py
+++ b/test/option/debug-memoizer.py
@@ -29,6 +29,7 @@ Test calling the --debug=memoizer option.
"""
import os
+import new
import string
import TestSCons
@@ -39,15 +40,22 @@ test = TestSCons.TestSCons(match = TestSCons.match_re)
class M:
def __init__(cls, name, bases, cls_dict):
- cls.has_metaclass = 1
-
-class A:
- __metaclass__ = M
+ cls.use_metaclass = 1
+ def fake_method(self):
+ pass
+ new.instancemethod(fake_method, None, cls)
try:
- has_metaclass = A.has_metaclass
+ class A:
+ __metaclass__ = M
+
+ use_metaclass = A.use_metaclass
except AttributeError:
- has_metaclass = None
+ use_metaclass = None
+ reason = 'no metaclasses'
+except TypeError:
+ use_metaclass = None
+ reason = 'new.instancemethod\\(\\) bug'
@@ -72,12 +80,8 @@ expect = [
"Node._children_get()",
]
-expect_no_metaclasses = """
-scons: warning: memoization is not supported in this version of Python \\(no metaclasses\\)
-""" + TestSCons.file_expr
-
-if has_metaclass:
+if use_metaclass:
def run_and_check(test, args, desc):
test.run(arguments = args)
@@ -92,6 +96,12 @@ if has_metaclass:
else:
+ expect_no_metaclasses = """
+scons: warning: memoization is not supported in this version of Python \\(%s\\)
+""" % reason
+
+ expect_no_metaclasses = expect_no_metaclasses + TestSCons.file_expr
+
def run_and_check(test, args, desc):
test.run(arguments = args, stderr = expect_no_metaclasses)
stdout = test.stdout()
diff --git a/test/option/stack-size.py b/test/option/stack-size.py
new file mode 100644
index 00000000..495f86dd
--- /dev/null
+++ b/test/option/stack-size.py
@@ -0,0 +1,357 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import string
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+isStackSizeAvailable = False
+try:
+ import threading
+ isStackSizeAvailable = hasattr(threading,'stack_size')
+except ImportError:
+ pass
+
+test.subdir('work1', 'work2')
+
+test.write('build.py', r"""
+import sys
+contents = open(sys.argv[2], 'rb').read()
+file = open(sys.argv[1], 'wb')
+file.write(contents)
+file.close()
+""")
+
+
+test.write(['work1', 'SConstruct'], """
+B = Builder(action = r'%(_python_)s ../build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+f1 = env.B(target = 'f1.out', source = 'f1.in')
+f2 = env.B(target = 'f2.out', source = 'f2.in')
+Requires(f2, f1)
+""" % locals())
+
+test.write(['work1', 'f1.in'], "f1.in\n")
+test.write(['work1', 'f2.in'], "f2.in\n")
+
+
+test.write(['work2', 'SConstruct'], """
+SetOption('stack_size', 128)
+B = Builder(action = r'%(_python_)s ../build.py $TARGETS $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+f1 = env.B(target = 'f1.out', source = 'f1.in')
+f2 = env.B(target = 'f2.out', source = 'f2.in')
+Requires(f2, f1)
+""" % locals())
+
+test.write(['work2', 'f1.in'], "f1.in\n")
+test.write(['work2', 'f2.in'], "f2.in\n")
+
+
+
+expected_stdout = test.wrap_stdout("""\
+%(_python_)s ../build.py f1.out f1.in
+%(_python_)s ../build.py f2.out f2.in
+""" % locals())
+
+re_expected_stdout = string.replace(expected_stdout, '\\', '\\\\')
+
+expect_unsupported = """
+scons: warning: Setting stack size is unsupported by this version of Python:
+ (('module' object|'threading' module) has no attribute 'stack_size'|stack_size)
+File .*
+"""
+
+
+#
+# Test without any options
+#
+test.run(chdir='work1',
+ arguments = '.',
+ stdout=expected_stdout,
+ stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1',
+ arguments = '-c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+#
+# Test with -j2
+#
+test.run(chdir='work1',
+ arguments = '-j2 .',
+ stdout=expected_stdout,
+ stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1',
+ arguments = '-j2 -c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+
+#
+# Test with --stack-size
+#
+test.run(chdir='work1',
+ arguments = '--stack-size=128 .',
+ stdout=expected_stdout,
+ stderr='')
+test.must_exist(['work1', 'f1.out'])
+test.must_exist(['work1', 'f2.out'])
+
+test.run(chdir='work1',
+ arguments = '--stack-size=128 -c .')
+test.must_not_exist(['work1', 'f1.out'])
+test.must_not_exist(['work1', 'f2.out'])
+
+#
+# Test with SetOption('stack_size', 128)
+#
+test.run(chdir='work2',
+ arguments = '.',
+ stdout=expected_stdout,
+ stderr='')
+test.must_exist(['work2', 'f1.out'])
+test.must_exist(['work2', 'f2.out'])
+
+test.run(chdir='work2',
+ arguments = '--stack-size=128 -c .')
+test.must_not_exist(['work2', 'f1.out'])
+test.must_not_exist(['work2', 'f2.out'])
+
+if isStackSizeAvailable:
+ #
+ # Test with -j2 --stack-size=128
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 -c .')
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=16
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 .',
+ match=TestSCons.match_re,
+ stdout=re_expected_stdout,
+ stderr="""
+scons: warning: Setting stack size failed:
+ size not valid: 16384 bytes
+File .*
+""")
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 -c .',
+ match=TestSCons.match_re,
+ stderr="""
+scons: warning: Setting stack size failed:
+ size not valid: 16384 bytes
+File .*
+""")
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 SetOption('stack_size', 128)
+ #
+ test.run(chdir='work2',
+ arguments = '-j2 .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work2', 'f1.out'])
+ test.must_exist(['work2', 'f2.out'])
+
+ test.run(chdir='work2',
+ arguments = '-j2 -c .')
+ test.must_not_exist(['work2', 'f1.out'])
+ test.must_not_exist(['work2', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=128 --warn=no-stack-size
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 --warn=no-stack-size -c .')
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=16 --warn=no-stack-size
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 --warn=no-stack-size -c .')
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --warn=no-stack-size SetOption('stack_size', 128)
+ #
+ test.run(chdir='work2',
+ arguments = '-j2 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work2', 'f1.out'])
+ test.must_exist(['work2', 'f2.out'])
+
+ test.run(chdir='work2',
+ arguments = '-j2 --warn=no-stack-size -c .')
+ test.must_not_exist(['work2', 'f1.out'])
+ test.must_not_exist(['work2', 'f2.out'])
+
+else:
+
+ #
+ # Test with -j2 --stack-size=128
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 .',
+ match=TestSCons.match_re,
+ stdout=re_expected_stdout,
+ stderr=expect_unsupported)
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 -c .',
+ match=TestSCons.match_re,
+ stderr=expect_unsupported)
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=16
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 .',
+ match=TestSCons.match_re,
+ stdout=re_expected_stdout,
+ stderr=expect_unsupported)
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 -c .',
+ match=TestSCons.match_re,
+ stderr=expect_unsupported)
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 SetOption('stack_size', 128)
+ #
+ test.run(chdir='work2',
+ arguments = '-j2 .',
+ match=TestSCons.match_re,
+ stdout=re_expected_stdout,
+ stderr=expect_unsupported)
+ test.must_exist(['work2', 'f1.out'])
+ test.must_exist(['work2', 'f2.out'])
+
+ test.run(chdir='work2',
+ arguments = '-j2 -c .',
+ match=TestSCons.match_re,
+ stderr=expect_unsupported)
+ test.must_not_exist(['work2', 'f1.out'])
+ test.must_not_exist(['work2', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=128 --warn=no-stack-size
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=128 --warn=no-stack-size -c .')
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --stack-size=16 --warn=no-stack-size
+ #
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work1', 'f1.out'])
+ test.must_exist(['work1', 'f2.out'])
+
+ test.run(chdir='work1',
+ arguments = '-j2 --stack-size=16 --warn=no-stack-size -c .')
+ test.must_not_exist(['work1', 'f1.out'])
+ test.must_not_exist(['work1', 'f2.out'])
+
+ #
+ # Test with -j2 --warn=no-stack-size SetOption('stack_size', 128)
+ #
+ test.run(chdir='work2',
+ arguments = '-j2 --warn=no-stack-size .',
+ stdout=expected_stdout,
+ stderr='')
+ test.must_exist(['work2', 'f1.out'])
+ test.must_exist(['work2', 'f2.out'])
+
+ test.run(chdir='work2',
+ arguments = '-j2 --warn=no-stack-size -c .')
+ test.must_not_exist(['work2', 'f1.out'])
+ test.must_not_exist(['work2', 'f2.out'])
+
+test.pass_test()
diff --git a/test/scons-time/func/file.py b/test/scons-time/func/file.py
index c9486c4b..079c125e 100644
--- a/test/scons-time/func/file.py
+++ b/test/scons-time/func/file.py
@@ -31,7 +31,7 @@ affect how the func subcommand processes things.
import TestSCons_time
-test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
+test = TestSCons_time.TestSCons_time()
try:
import pstats
@@ -56,22 +56,43 @@ prefix = 'foo-001'
expect1 = r'\d.\d\d\d prof1\.py:1\(_main\)' + '\n'
-test.run(arguments = 'func -f st1.conf', stdout = expect1)
+test.run(arguments = 'func -f st1.conf',
+ match = TestSCons_time.match_re,
+ stdout = expect1)
test.write('st2.conf', """\
prefix = 'foo'
title = 'ST2.CONF TITLE'
+vertical_bars = (
+ ( 1.4, 7, None ),
+ ( 1.5, 7, "label 1.5" ),
+ ( 1.6, 7, "label 1.6" ),
+)
""")
expect2 = \
r"""set title "ST2.CONF TITLE"
set key bottom left
-plot '-' title "Startup" with lines lt 1
+set label 3 "label 1.5" at 0.5,1.5 right
+set label 4 "label 1.6" at 0.6,1.5 right
+plot '-' title "Startup" with lines lt 1, \
+ '-' notitle with lines lt 7, \
+ '-' title "label 1.5" with lines lt 7, \
+ '-' title "label 1.6" with lines lt 7
# Startup
1 0.000
2 0.000
e
+1.4 0
+1.4 1
+e
+1.5 0
+1.5 1
+e
+1.6 0
+1.6 1
+e
"""
test.run(arguments = 'func --file st2.conf --fmt gnuplot', stdout = expect2)
diff --git a/test/scons-time/mem/file.py b/test/scons-time/mem/file.py
index a236df7d..a1e71812 100644
--- a/test/scons-time/mem/file.py
+++ b/test/scons-time/mem/file.py
@@ -53,16 +53,23 @@ test.run(arguments = 'mem -f st1.conf', stdout = expect1)
test.write('st2.conf', """\
prefix = 'foo'
title = 'ST2.CONF TITLE'
+vertical_bars = (
+ ( 1.5, 7, None ),
+)
""")
expect2 = \
r"""set title "ST2.CONF TITLE"
set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+ '-' notitle with lines lt 7
# Startup
1 4000.000
2 4000.000
e
+1.5 0
+1.5 4800
+e
"""
test.run(arguments = 'mem --file st2.conf --fmt gnuplot', stdout = expect2)
diff --git a/test/scons-time/obj/file.py b/test/scons-time/obj/file.py
index c8813973..3cf8e742 100644
--- a/test/scons-time/obj/file.py
+++ b/test/scons-time/obj/file.py
@@ -53,16 +53,23 @@ test.run(arguments = 'obj -f st1.conf Node.FS.Base', stdout = expect1)
test.write('st2.conf', """\
prefix = 'foo'
title = 'ST2.CONF TITLE'
+vertical_bars = (
+ ( 1.5, 7, None ),
+)
""")
expect2 = \
r"""set title "ST2.CONF TITLE"
set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+ '-' notitle with lines lt 7
# Startup
1 16040.000
2 16040.000
e
+1.5 0
+1.5 18000
+e
"""
test.run(arguments = 'obj --file st2.conf --fmt gnuplot Node.FS.Base', stdout = expect2)
diff --git a/test/scons-time/run/aegis.py b/test/scons-time/run/aegis.py
index 8f378cbd..641f1291 100644
--- a/test/scons-time/run/aegis.py
+++ b/test/scons-time/run/aegis.py
@@ -59,22 +59,9 @@ test.must_exist('foo-329-0.log',
'foo-329-2.log',
'foo-329-2.prof')
-def tempdir_re(*args):
- import os
- import os.path
- import string
- import tempfile
-
- sep = re.escape(os.sep)
- args = (tempfile.gettempdir(), 'scons-time-aegis-',) + args
- x = apply(os.path.join, args)
- x = re.escape(x)
- x = string.replace(x, 'aegis\\-', 'aegis\\-[^%s]*' % sep)
- return x
-
expect = [
- tempdir_re('src', 'script', 'scons.py'),
- 'SCONS_LIB_DIR = %s' % tempdir_re('src', 'engine'),
+ test.tempdir_re('src', 'script', 'scons.py'),
+ 'SCONS_LIB_DIR = %s' % test.tempdir_re('src', 'engine'),
]
content = test.read(test.workpath('foo-321-2.log'))
diff --git a/test/scons-time/run/config/archive_list.py b/test/scons-time/run/config/archive_list.py
index 8d48d262..8ddde3d0 100644
--- a/test/scons-time/run/config/archive_list.py
+++ b/test/scons-time/run/config/archive_list.py
@@ -38,9 +38,11 @@ test.write_fake_scons_py()
test.write_sample_project('foo.tar.gz')
test.write('config', """\
-archive_list = ['foo.tar.gz']
+archive_list = ['foo.tar.gz', 'foo-file']
""")
+test.write('foo-file', "foo-file\n")
+
test.run(arguments = 'run -f config')
test.must_exist('foo-000-0.log',
@@ -50,6 +52,8 @@ test.must_exist('foo-000-0.log',
'foo-000-2.log',
'foo-000-2.prof')
+test.must_exist('foo-file')
+
test.write_sample_project('bar.tar.gz')
diff --git a/test/scons-time/run/option/quiet.py b/test/scons-time/run/option/quiet.py
index f5a3d8c0..453829cb 100644
--- a/test/scons-time/run/option/quiet.py
+++ b/test/scons-time/run/option/quiet.py
@@ -37,28 +37,11 @@ python = TestSCons_time.python
test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
test.diff_function = TestSCons_time.diff_re
-
-def tempdir_re(*args):
- import os,sys
- import os.path
- import string
- import tempfile
-
- sep = re.escape(os.sep)
- args = (tempfile.gettempdir(), 'scons-time-',) + args
- x = apply(os.path.join, args)
- x = re.escape(x)
- x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
- if sys.platform=='darwin':
- # OSX has /tmp in /private/tmp.
- x = '(/private)?' + x
- return x
-
scons_py = re.escape(test.workpath('src', 'script', 'scons.py'))
src_engine = re.escape(test.workpath('src', 'engine'))
-tmp_scons_time = tempdir_re()
-tmp_scons_time_foo = tempdir_re('foo')
+tmp_scons_time = test.tempdir_re()
+tmp_scons_time_foo = test.tempdir_re('foo')
test.write_fake_scons_py()
diff --git a/test/scons-time/run/option/verbose.py b/test/scons-time/run/option/verbose.py
index fb95dab5..935e2a96 100644
--- a/test/scons-time/run/option/verbose.py
+++ b/test/scons-time/run/option/verbose.py
@@ -38,28 +38,11 @@ _python_ = re.escape(TestSCons_time._python_)
test = TestSCons_time.TestSCons_time(match = TestSCons_time.match_re)
test.diff_function = TestSCons_time.diff_re
-
-def tempdir_re(*args):
- import os,sys
- import os.path
- import string
- import tempfile
-
- sep = re.escape(os.sep)
- args = (tempfile.gettempdir(), 'scons-time-',) + args
- x = apply(os.path.join, args)
- x = re.escape(x)
- x = string.replace(x, 'time\\-', 'time\\-[^%s]*' % sep)
- if sys.platform=='darwin':
- # OSX has /tmp in /private/tmp.
- x = '(/private)?' + x
- return x
-
scons_py = re.escape(test.workpath('src', 'script', 'scons.py'))
src_engine = re.escape(test.workpath('src', 'engine'))
-tmp_scons_time = tempdir_re()
-tmp_scons_time_foo = tempdir_re('foo')
+tmp_scons_time = test.tempdir_re()
+tmp_scons_time_foo = test.tempdir_re('foo')
test.write_fake_scons_py()
diff --git a/test/scons-time/run/subversion.py b/test/scons-time/run/subversion.py
index 3839999e..757f6dff 100644
--- a/test/scons-time/run/subversion.py
+++ b/test/scons-time/run/subversion.py
@@ -60,22 +60,9 @@ test.must_exist('foo-716-0.log',
'foo-716-2.log',
'foo-716-2.prof')
-def tempdir_re(*args):
- import os
- import os.path
- import string
- import tempfile
-
- sep = re.escape(os.sep)
- args = (tempfile.gettempdir(), 'scons-time-svn-',) + args
- x = apply(os.path.join, args)
- x = re.escape(x)
- x = string.replace(x, 'svn\\-', 'svn\\-[^%s]*' % sep)
- return x
-
expect = [
- tempdir_re('src', 'script', 'scons.py'),
- 'SCONS_LIB_DIR = %s' % tempdir_re('src', 'engine'),
+ test.tempdir_re('src', 'script', 'scons.py'),
+ 'SCONS_LIB_DIR = %s' % test.tempdir_re('src', 'engine'),
]
content = test.read(test.workpath('foo-617-2.log'), mode='r')
diff --git a/test/scons-time/time/empty.py b/test/scons-time/time/empty.py
new file mode 100644
index 00000000..7542bfcc
--- /dev/null
+++ b/test/scons-time/time/empty.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the time subcommand doesn't fail and prints an appropriate
+error message if a log file is empty.
+"""
+
+import TestSCons_time
+
+test = TestSCons_time.TestSCons_time()
+
+
+header = ' Total SConscripts SCons commands\n'
+
+lines = []
+
+line_fmt = ' 11.123456 22.234567 33.345678 44.456789 %s\n'
+empty_fmt = ' %s\n'
+
+for i in xrange(9):
+ logfile_name = 'foo-%s-0.log' % i
+ if i == 5:
+ test.write(test.workpath(logfile_name), "")
+ lines.append(empty_fmt % logfile_name)
+ else:
+ test.fake_logfile(logfile_name)
+ lines.append(line_fmt % logfile_name)
+
+expect = [header] + lines
+
+test.run(arguments = 'time foo-*.log',
+ stdout = ''.join(expect),
+ stderr = "file 'foo-5-0.log' has no contents!\n")
+
+expect = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+0 11.123456
+1 11.123456
+2 11.123456
+3 11.123456
+4 11.123456
+6 11.123456
+7 11.123456
+8 11.123456
+e
+"""
+
+stderr = "file 'foo-5-0.log' has no contents!\n"
+
+test.run(arguments = 'time --fmt gnuplot --which total foo-*.log',
+ stdout = expect,
+ stderr = stderr)
+
+expect = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+e
+"""
+
+test.run(arguments = 'time --fmt gnuplot foo-5-0.log',
+ stdout = expect,
+ stderr = stderr)
+
+test.pass_test()
diff --git a/test/scons-time/time/file.py b/test/scons-time/time/file.py
index f4046c9f..96bd035b 100644
--- a/test/scons-time/time/file.py
+++ b/test/scons-time/time/file.py
@@ -53,16 +53,23 @@ test.run(arguments = 'time -f st1.conf', stdout = expect1)
test.write('st2.conf', """\
prefix = 'foo'
title = 'ST2.CONF TITLE'
+vertical_bars = (
+ ( 1.5, 7, None ),
+)
""")
expect2 = \
r"""set title "ST2.CONF TITLE"
set key bottom left
-plot '-' title "Startup" with lines lt 1
+plot '-' title "Startup" with lines lt 1, \
+ '-' notitle with lines lt 7
# Startup
1 11.123456
2 11.123456
e
+1.5 0
+1.5 12
+e
"""
test.run(arguments = 'time --file st2.conf --fmt gnuplot', stdout = expect2)
diff --git a/test/scons-time/time/no-result.py b/test/scons-time/time/no-result.py
new file mode 100644
index 00000000..ca345b2f
--- /dev/null
+++ b/test/scons-time/time/no-result.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that the time subcommand's --which option doesn't fail, and prints
+an appropriate error message, if a log file doesn't have its specific
+requested results.
+"""
+
+import TestSCons_time
+
+test = TestSCons_time.TestSCons_time()
+
+
+header = """\
+set key bottom left
+plot '-' title "Startup" with lines lt 1
+# Startup
+"""
+
+footer = """\
+e
+"""
+
+line_fmt = "%s 11.123456\n"
+
+lines = []
+
+for i in xrange(9):
+ logfile_name = 'foo-%s-0.log' % i
+ if i == 5:
+ test.write(test.workpath(logfile_name), "NO RESULTS HERE!\n")
+ else:
+ test.fake_logfile(logfile_name)
+ lines.append(line_fmt % i)
+
+expect = [header] + lines + [footer]
+
+stderr = "file 'foo-5-0.log' has no results!\n"
+
+
+test.run(arguments = 'time --fmt gnuplot --which total foo*.log',
+ stdout = ''.join(expect),
+ stderr = stderr)
+
+expect = [header] + [footer]
+
+test.run(arguments = 'time --fmt gnuplot foo-5-0.log',
+ stdout = ''.join(expect),
+ stderr = stderr)
+
+test.pass_test()
diff --git a/test/subclassing.py b/test/subclassing.py
index 18be1bcc..31632a97 100644
--- a/test/subclassing.py
+++ b/test/subclassing.py
@@ -25,7 +25,9 @@
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
-Verify that we can trivially subclass our "public" classes.
+Verify that we can trivially subclass our "public" classes. Also
+verify that we can use a trivial subclass of new-style str classes
+as well as UserString as Builder input.
"""
import TestSCons
@@ -33,6 +35,8 @@ import TestSCons
test = TestSCons.TestSCons()
test.write('SConstruct', """
+copy_action = Copy('$TARGET', '$SOURCE')
+
# Some day, we'd probably like people to be able to subclass Action and
# Builder, but that's going to take some serious class-hackery to turn
# our factory function into the class itself.
@@ -45,18 +49,31 @@ class my_Scanner(Scanner):
class my_Environment(Environment):
pass
env = my_Environment()
-env.Program('hello', 'hello.c')
-""")
+env.Command('f0.out', 'f0.in', copy_action)
+
+from UserString import UserString
+try:
+ class mystr(str):
+ pass
+except TypeError:
+ class mystr(UserString):
+ pass
-test.write('hello.c', """\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[]) {
- printf("hello.c\\n");
-}
+Command(mystr('f1.out'), mystr('f1.in'), copy_action)
+Command(UserString('f2.out'), UserString('f2.in'), copy_action)
+
+Install(mystr('install'), mystr('f1.out'))
+Install(mystr('install'), UserString('f2.out'))
""")
+test.write('f0.in', "f0.in\n")
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
test.run(arguments = '.')
+test.must_match('f0.out', "f0.in\n")
+test.must_match(['install', 'f1.out'], "f1.in\n")
+test.must_match(['install', 'f2.out'], "f2.in\n")
+
test.pass_test()
diff --git a/test/timestamp-fallback.py b/test/timestamp-fallback.py
index 9d89d704..07749951 100644
--- a/test/timestamp-fallback.py
+++ b/test/timestamp-fallback.py
@@ -79,7 +79,8 @@ test.write('f2.in', "f2.in\n")
test.write('f3.in', "f3.in\n")
test.write('f4.in', "f4.in\n")
-test.run(arguments = 'f1.out f3.out')
+test.run(arguments = 'f1.out f3.out',
+ stderr = None)
test.run(arguments = 'f1.out f2.out f3.out f4.out',
stdout = test.wrap_stdout("""\
@@ -87,7 +88,8 @@ scons: `f1.out' is up to date.
build(["f2.out"], ["f2.in"])
scons: `f3.out' is up to date.
build(["f4.out"], ["f4.in"])
-"""))
+"""),
+ stderr = None)
os.utime(test.workpath('f1.in'),
(os.path.getatime(test.workpath('f1.in')),
@@ -102,7 +104,8 @@ build(["f1.out"], ["f1.in"])
scons: `f2.out' is up to date.
build(["f3.out"], ["f3.in"])
scons: `f4.out' is up to date.
-"""))
+"""),
+ stderr = None)
test.pass_test()
diff --git a/timings/CPPPATH/SConstruct b/timings/CPPPATH/SConstruct
new file mode 100644
index 00000000..728db9cb
--- /dev/null
+++ b/timings/CPPPATH/SConstruct
@@ -0,0 +1,66 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for testing the timing of searching long lists of
+CPPPATH directories.
+
+We create 100 on-disk directories, with a single .h file in the last
+directory in the list. We set CPPPATH to a list of Dir Nodes for the
+created directories. The .c file we create #includes the .h file to be
+found in the last directory in the list.
+"""
+
+import os
+import os.path
+
+dir_cnt = 100
+
+dir_list = map(lambda t: 'inc_%03d' % t, xrange(dir_cnt))
+
+for dir in dir_list:
+ if not os.path.isdir(dir):
+ os.mkdir(dir)
+
+foo_h = 'inc_099/foo.h'
+
+if not os.path.isfile(foo_h):
+ open(foo_h, 'w').write('#define FOO 1\n')
+
+contents = """\
+#include "foo.h"
+void
+foo(void)
+{
+ ;
+}
+"""
+
+if not os.path.isfile('foo.c'):
+ open('foo.c', 'w').write(contents)
+
+inc_list = map(lambda d: Dir(d), dir_list)
+
+env = Environment(CPPPATH = inc_list)
+
+env.Object( 'foo.c' )
diff --git a/timings/CPPPATH/st.conf b/timings/CPPPATH/st.conf
new file mode 100644
index 00000000..6507ea24
--- /dev/null
+++ b/timings/CPPPATH/st.conf
@@ -0,0 +1,44 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+scons-time.py configuration file for the "CPPPATH" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+ 1224, # Don't create a Node for every file we try to find during scan.
+ 1349, # More efficient checking for on-disk file entries.
+ 1407, # Use a Dir scanner instead of a hard-coded method.
+ 1433, # Remove unnecessary creation of RCS and SCCS Node.Dir nodes.
+ 1703, # Lobotomize Memoizer.
+ 2380, # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+ SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)
diff --git a/timings/JTimer/SConstruct b/timings/JTimer/SConstruct
new file mode 100644
index 00000000..e1e38d20
--- /dev/null
+++ b/timings/JTimer/SConstruct
@@ -0,0 +1,54 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for timing how we evaluate long chains of
+dependencies, specifically when -j is used.
+
+We set up a chain of 100 targets that get built from a Python function
+action with no source files (equivalent to "echo junk > $TARGET").
+Each target explicitly depends on the next target in turn, so the
+Taskmaster will do a deep walk of the dependency graph.
+
+This test case was contributed by Kevin Massey. Prior to revision 1468,
+we had a serious O(N^2) problem in the Taskmaster when handling long
+dependency chains like this. That was fixed by adding reference counts
+to the Taskmaster so it could be smarter about not re-evaluating Nodes.
+"""
+
+target_cnt = 100
+
+env = Environment()
+
+def write_file( env, target, source ):
+ path_target = env.File( target ).path
+ outfile = open( path_target, 'w' )
+ outfile.write( 'junk' )
+ outfile.close()
+
+list = []
+for i in range( target_cnt ):
+ target = 'target_%03d' % i
+ env.Command( target, [], write_file )
+ env.Depends( target, list )
+ list.append( target )
diff --git a/timings/JTimer/st.conf b/timings/JTimer/st.conf
new file mode 100644
index 00000000..cf1ecbfe
--- /dev/null
+++ b/timings/JTimer/st.conf
@@ -0,0 +1,48 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+scons-time.py configuration file for the "JTimer" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+targets = '-j2'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+ 1261, # Fix -j re-scanning built files for implicit deps.
+ 1307, # Move signature Node tranlation of rel_paths into the class.
+ 1407, # Use a Dir scanner instead of a hard-coded method.
+ 1435, # Don't prep .sconsign dependencies until needed.
+ 1468, # Use waiting-Node reference counts to speed up Taskmaster.
+ 1703, # Lobotomize Memoizer.
+ 1706, # Fix _doLookup value-cache misspellings.
+ 2380, # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+ SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)
diff --git a/timings/SCons_Bars.py b/timings/SCons_Bars.py
new file mode 100644
index 00000000..c56fb363
--- /dev/null
+++ b/timings/SCons_Bars.py
@@ -0,0 +1,118 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+A quick module for central collection of information about which
+Subversion revisions are important for performance implications.
+"""
+
+class Bars(dict):
+ """
+ Dictionary subclass for mapping revision numbers to labels describing
+ each revision.
+
+ We provide two extensions: a .color attribute (for the default
+ color) and a .gnuplot() method (for returning a list of revisions
+ in the tuple format that scons-time uses to describe vertical bars).
+ """
+ def __init__(self, dict=None, color=None, **kwargs):
+ super(Bars, self).__init__(dict, **kwargs)
+ self.color = color
+ def gnuplot(self, color=None, labels=False, revs=None):
+ if color is None:
+ color = self.color
+ if revs is None:
+ revs = self.keys()
+ revs.sort()
+ if labels:
+ result = [ (r, color, None, self[r]) for r in revs ]
+ else:
+ result = [ (r, color, None, None) for r in revs ]
+ return tuple(result)
+
+# The Release_Bars dictionary records the Subversion revisions that
+# correspond to each official SCons release.
+
+Release_Bars = Bars(
+ color = 7,
+ dict = {
+ 1232 : '0.96.90',
+ 1344 : '0.96.91',
+ 1435 : '0.96.92',
+ 1674 : '0.96.93',
+ 1765 : '0.96.94',
+ 1835 : '0.96.95',
+ 1882 : '0.96.96',
+ 1901 : '0.97',
+ 2242 : '0.97.0d20070809',
+ 2454 : '0.97.0d20070918',
+ 2527 : '0.97.0d20071212',
+ },
+)
+
+
+# The Revisions_Bars dictionary records the Subversion revisions that
+# correspond to "interesting" changes in timing. This is essentially the
+# global list of interesting changes. Individual timing configurations
+# typically only display bars for a subset of these, the ones that
+# actually affect their configuration.
+#
+# Note that the default behavior of most of the st.conf files is to
+# *not* display the labels for each of these lines, since they're long
+# and verbose. So in practice they function as comments describing the
+# changes that have timing impacts on various configurations.
+
+Revision_Bars = Bars(
+ color = 5,
+ dict = {
+ 1220 : 'Use WeakValueDicts in the Memoizer to reduce memory use.',
+ 1224 : 'Don\'t create a Node for every file we try to find during scan.',
+ 1231 : 'Don\'t pick same-named directories in a search path.',
+ 1241 : 'Optimize out N*M suffix matching in Builder.py.',
+ 1245 : 'Reduce gen_binfo() time for long source lists.',
+ 1261 : 'Fix -j re-scanning built files for implicit deps.',
+ 1262 : 'Match Entries when searching paths for Files or Dirs.',
+ 1273 : 'Store paths in .sconsign relative to target directory.',
+ 1282 : 'Cache result from rel_path().',
+ 1307 : 'Move signature Node tranlation of rel_paths into the class.',
+ 1346 : 'Give subst logic its own module.',
+ 1349 : 'More efficient checking for on-disk file entries.',
+ 1407 : 'Use a Dir scanner instead of a hard-coded method.',
+ 1433 : 'Remove unnecessary creation of RCS and SCCS Node.Dir nodes.',
+ 1435 : 'Don\'t convert .sconsign dependencies to Nodes until needed.',
+ 1468 : 'Use waiting-Node reference counts to speed up Taskmaster.',
+ 1477 : 'Delay disambiguation of Node.FS.Entry into File/Dir.',
+ 1533 : 'Fix some disambiguation-delay ramifications.',
+ 1655 : 'Reduce unnecessary calls to Node.FS.disambiguate().',
+ 1703 : 'Lobotomize Memoizer.',
+ 1706 : 'Fix _doLookup value-cache misspellings.',
+ 1712 : 'PathList, restore caching of Builder source suffixes.',
+ 1724 : 'Cache Node.FS.find_file() and Node.FS.Dir.srcdir_find_file().',
+ 1727 : 'Cache Executor methods, reduce calls when scanning.',
+ 1752 : 'Don\'t cache Builder source suffixes too early.',
+ 1790 : 'Clean up various module imports (pychecker fixes).',
+ 1794 : 'Un-fix various later-Python-version pychecker "fixes".',
+ 1828 : 'Speed up Builder suffix-matching (SuffixMap).',
+ 2380 : 'The Big Signature Refactoring hits branches/core.',
+ },
+)
diff --git a/timings/hundred/SConstruct b/timings/hundred/SConstruct
new file mode 100644
index 00000000..2332d732
--- /dev/null
+++ b/timings/hundred/SConstruct
@@ -0,0 +1,52 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+This configuration is for timing how we handle the NxM interaction when
+we build a lot of targets from a lot of source files.
+
+We create a list of 100 target files that will each be built by copying
+a file from a corresponding list of 100 source files. The source
+files themselves are each built by a Python function action that's the
+equivalent of "echo contents > $TARGET".
+"""
+
+target_cnt = 100
+
+env = Environment()
+
+def create_file( env, target, source ):
+ t = str(target[0])
+ open( t, 'w' ).write('contents\n')
+
+source_list = map(lambda t: 'source_%03d' % t, xrange(target_cnt))
+target_list = map(lambda t: 'target_%03d' % t, xrange(target_cnt))
+
+for source in source_list:
+ env.Command( source, [], create_file )
+
+def copy_files( env, target, source ):
+ for t, s in zip(target, source):
+ open(str(t), 'w').write(open(str(s), 'r').read())
+
+env.Command( target_list, source_list, copy_files )
diff --git a/timings/hundred/st.conf b/timings/hundred/st.conf
new file mode 100644
index 00000000..adfb09eb
--- /dev/null
+++ b/timings/hundred/st.conf
@@ -0,0 +1,47 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+"""
+scons-time.py configuration file for the "hundred" timing test.
+"""
+
+archive_list = [ 'SConstruct' ]
+subdir = '.'
+
+import sys
+sys.path.insert(0, '..')
+import SCons_Bars
+
+revs = [
+ 1220, # Use WeakValueDicts in the Memoizer to reduce memory use.
+ 1307, # Move signature Node tranlation of rel_paths into the class.
+ 1435, # Fix Debug.caller() directory separators.
+ 1477, # Delay disambiguation of Node.FS.Entry into File/Dir.
+ 1655, # Reduce unnecessary calls to Node.FS.disambiguate().
+ 1703, # Lobotomize Memoizer.
+ 1727, # Cache Executor methods, reduce calls when scanning.
+ 2380, # The Big Signature Refactoring hits branches/core.
+]
+
+vertical_bars = SCons_Bars.Release_Bars.gnuplot(labels=True) + \
+ SCons_Bars.Revision_Bars.gnuplot(labels=False, revs=revs)