diff options
174 files changed, 9401 insertions, 1315 deletions
diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index f5e1c71..8bf054b 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 d6b21ad..acc63d4 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 b1fdbc1..6b6f5ed 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 102181e..f3ea49a 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 b2de002..07832a9 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 526a5a3..ae25274 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 c23e6ae..e590368 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 c8dc7d3..81b54e6 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 ca01607..7327f21 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 52323d2..093fbd9 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 c2c1158..cd4bf6a 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 06030e3..2ad4bef 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 9b2b4b4..7caee61 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 fcf8c5a..33899f6 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 c3d30cb..3cd47ef 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 cf2d0eb..02ad332 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 3f64d43..4ffff7a 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 1cb0cf9..7222042 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 b28aaaf..7b51409 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 5f056e8..c432581 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 c2b4181..c4a5001 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 7102f30..bceeebf 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 d0843d1..1a3c010 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 fe42035..8e9a3f8 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 f252151..4ca34e0 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 be645ca..ae00fc0 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 1d4e9f7..afdabe1 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 ae3a77e..c5de498 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 601c5eb..f7d33f8 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 276570e..4356c7a 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 5a0b383..bfbcd5d 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 c0a38b5..ceb9bf5 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 64d6d77..6e9286a 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 c8ab155..924b271 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 0000000..e38c400 --- /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 97e0b19..bcbd0a1 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 46ece27..8f7116d 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 989f1dd..7a565ba 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 b6e5b71..c064164 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 3bb4225..9db8138 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 a65a4af..532301f 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 0000000..f3db693 --- /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 0000000..ba0fe76 --- /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 673c848..02cc52a 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 4f221c0..6594ecc 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 a0d730e..9e8fefa 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 be1a81a..b60aa87 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 25f3564..42eabaf 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 105f42e..d67cddb 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 4b48e0b..ed5c8ee 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 8ca1b89..eba49a7 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 bbae25e..c3156a3 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 34f60cb..0b648e8 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 2db0603..aa648b1 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 258de0f..08ce1f2 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 3e8085b..44d6fa8 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 b1d39ec..5354959 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 47ae3be..91e3776 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 0000000..d6c1035 --- /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 8620936..cdd6a3a 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 0959e2c..33fd01d 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 284443a..d651ba9 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 0d6e6ac..c446cad 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 0000000..00aa688 --- /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 0000000..5c302c3 --- /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 c38f857..7a169e8 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 0000000..c9b60c3 --- /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 0000000..b6b8b44 --- /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 0000000..9adb206 --- /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 0000000..7c80f22 --- /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 0000000..7e5326f --- /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 a6f2fa5..1ce114c 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 8b443f8..8b443f8 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 0000000..5c69820 --- /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 b9ff1ff..956caa7 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 0000000..bcdcb7c --- /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 0000000..d6e018b --- /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 0000000..b2a9557 --- /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 0000000..05d1e9d --- /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 4cf9310..38a7915 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 0000000..fc05b9a --- /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 0000000..36ebf2f --- /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 0000000..d6205e2 --- /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 0000000..8c30314 --- /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 0000000..4c78f69 --- /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 0000000..dab5159 --- /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 0000000..0fb8435 --- /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 0000000..8fd3f92 --- /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 0000000..c1fe487 --- /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 0000000..4f4f80d --- /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 0000000..df06d5d --- /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 0000000..ef4c578 --- /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 0000000..7b7aa4b --- /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 0000000..4c02fa7 --- /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 0000000..a2935a7 --- /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 0000000..29bba88 --- /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 0000000..f15605e --- /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 0000000..f5ee1e3 --- /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 0000000..167d581 --- /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 0000000..e85b9ea --- /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 0000000..9d5e8a2 --- /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 0000000..23b9ad9 --- /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 0000000..96e7d4a --- /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 0000000..2b77378 --- /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 0000000..84f70e8 --- /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 f0951c6..ee552f4 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 f7d9fca..a3f2ec4 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 3939d98..03a222b 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 7723224..5962b94 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 045fb7b..e287054 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 0ae7dea..11f7c2c 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 ecd3737..95abd33 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 069e228..87e90ad 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 3d3d47a..3c0e1ed 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 4ac3d96..f6d93c7 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 f2b629a..5bd8e2f 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 2ab1804..f9721c2 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 ed5e0d6..e8a5655 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 5a451eb..a92bac5 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 d185b4d..9cac759 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 0000000..795689c --- /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 8d2506f..63d1d92 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 8df5e09..5477a2d 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 7fd4f9b..7fd4f9b 100644 --- a/test/LIBPATH.py +++ b/test/Libs/LIBPATH.py diff --git a/test/LIBPREFIX.py b/test/Libs/LIBPREFIX.py index a71e967..a71e967 100644 --- a/test/LIBPREFIX.py +++ b/test/Libs/LIBPREFIX.py diff --git a/test/LIBPREFIXES.py b/test/Libs/LIBPREFIXES.py index 1e5d6c2..1e5d6c2 100644 --- a/test/LIBPREFIXES.py +++ b/test/Libs/LIBPREFIXES.py diff --git a/test/LIBS.py b/test/Libs/LIBS.py index e9f6545..e9f6545 100644 --- a/test/LIBS.py +++ b/test/Libs/LIBS.py diff --git a/test/LIBSUFFIX.py b/test/Libs/LIBSUFFIX.py index a71e967..a71e967 100644 --- a/test/LIBSUFFIX.py +++ b/test/Libs/LIBSUFFIX.py diff --git a/test/LIBSUFFIXES.py b/test/Libs/LIBSUFFIXES.py index 29cbb18..29cbb18 100644 --- a/test/LIBSUFFIXES.py +++ b/test/Libs/LIBSUFFIXES.py diff --git a/test/Library.py b/test/Libs/Library.py index 4bcb2c7..4bcb2c7 100644 --- a/test/Library.py +++ b/test/Libs/Library.py diff --git a/test/SHLIBPREFIX.py b/test/Libs/SHLIBPREFIX.py index 87b92fa..87b92fa 100644 --- a/test/SHLIBPREFIX.py +++ b/test/Libs/SHLIBPREFIX.py diff --git a/test/SHLIBSUFFIX.py b/test/Libs/SHLIBSUFFIX.py index dc88e3b..dc88e3b 100644 --- a/test/SHLIBSUFFIX.py +++ b/test/Libs/SHLIBSUFFIX.py diff --git a/test/SharedLibrary.py b/test/Libs/SharedLibrary.py index f8447ca..f8447ca 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 0000000..f9d1471 --- /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 5243fbf..4a8e1ed 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 0000000..00f3ee2 --- /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 2878136..9936490 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 baa1e6f..ef94dd6 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 6e8bbd6..67ef605 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 fc87904..258d9bd 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 9216a8b..0f80ace 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 ebe6a83..bf8edff 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 0000000..0d89ebe --- /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 dd2c938..ef91189 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 0000000..8035957 --- /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 d4e2d79..686263d 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 4934570..0e5f156 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 e25fbd5..0000000 --- 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 8c7c4ab..28c9a0a 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 9c5d3af..85948f9 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 0a46606..4a460a2 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 172cbb0..70857e6 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 8c8b2cf..ad35b5d 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 0000000..495f86d --- /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 c9486c4..079c125 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 a236df7..a1e7181 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 c881397..3cf8e74 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 8f378cb..641f129 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 8d48d26..8ddde3d 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 f5a3d8c..453829c 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 fb95dab..935e2a9 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 3839999..757f6df 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 0000000..7542bfc --- /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 f4046c9..96bd035 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 0000000..ca345b2 --- /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 18be1bc..31632a9 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 9d89d70..0774995 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 0000000..728db9c --- /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 0000000..6507ea2 --- /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 0000000..e1e38d2 --- /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 0000000..cf1ecbf --- /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 0000000..c56fb36 --- /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 0000000..2332d73 --- /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 0000000..adfb09e --- /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) |