'"%(python)s" bootstrap.py %%s' % locals()
]
+build_test_baseline = []
+
cmd = '"%(python)s" runtest.py -q --noqmtest %%s' % locals()
test_inputs = [
test.match_re_dotall("actual 1\nactual 2\n", regex_string)
test.match_re_dotall(["actual 1\n", "actual 2\n"], list_of_regexes)
+ test.tempdir()
+ test.tempdir('temporary-directory')
+
test.sleep()
test.sleep(seconds)
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.23.D001 2006/11/30 13:57:29 knight"
-__version__ = "0.23"
+__revision__ = "TestCmd.py 0.26.D001 2007/08/20 21:58:58 knight"
+__version__ = "0.26"
import os
import os.path
return type(e) is types.StringType or isinstance(e, UserString)
tempfile.template = 'testcmd.'
+if os.name in ('posix', 'nt'):
+ tempfile.template = 'testcmd.' + str(os.getpid()) + '.'
+else:
+ tempfile.template = 'testcmd.'
re_space = re.compile('\s')
subdir = None,
verbose = None,
match = None,
- combine = 0):
+ combine = 0,
+ universal_newlines = 1):
self._cwd = os.getcwd()
self.description_set(description)
self.program_set(program)
verbose = 0
self.verbose_set(verbose)
self.combine = combine
+ self.universal_newlines = universal_newlines
if not match is None:
self.match_func = match
else:
interpreter = None,
arguments = None,
chdir = None,
- stdin = 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.
cmd_string = string.join(map(self.escape, cmd), ' ')
if self.verbose:
sys.stderr.write(cmd_string + "\n")
+ if universal_newlines is None:
+ universal_newlines = self.universal_newlines
+
try:
- p = popen2.Popen3(cmd, 1)
- except AttributeError:
- if sys.platform == 'win32' and cmd_string[0] == '"':
- cmd_string = '"' + cmd_string + '"'
- (tochild, fromchild, childerr) = os.popen3(' ' + cmd_string)
- if stdin:
- if is_List(stdin):
- for line in stdin:
- tochild.write(line)
- else:
- tochild.write(stdin)
- tochild.close()
- out = fromchild.read()
- err = childerr.read()
- if self.combine:
- self._stdout.append(out + err)
+ 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:
- self._stdout.append(out)
- self._stderr.append(err)
- fromchild.close()
- self.status = childerr.close()
- if not self.status:
- self.status = 0
- except:
- raise
+ p = Popen3(cmd, 1)
+ p.stdin = p.tochild
+ p.stdout = p.fromchild
+ p.stderr = p.childerr
else:
- if stdin:
- if is_List(stdin):
- for line in stdin:
- p.tochild.write(line)
- else:
- p.tochild.write(stdin)
- p.tochild.close()
- out = p.fromchild.read()
- err = p.childerr.read()
- if self.combine:
- self._stdout.append(out + err)
+ p = subprocess.Popen(cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=universal_newlines)
+
+ if stdin:
+ if is_List(stdin):
+ for line in stdin:
+ p.stdin.write(line)
else:
- self._stdout.append(out)
- self._stderr.append(err)
- self.status = p.wait()
+ p.stdin.write(stdin)
+ p.stdin.close()
+
+ out = p.stdout.read()
+ err = p.stderr.read()
+ try:
+ p.close_output()
+ except AttributeError:
+ p.stdout.close()
+ p.stderr.close()
+
+ 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:
link = self.canonicalize(link)
os.symlink(target, link)
+ def tempdir(self, path=None):
+ """Creates a temporary directory.
+ A unique directory name is generated if no path name is specified.
+ The directory is created, and will be removed when the TestCmd
+ object is destroyed.
+ """
+ if path is None:
+ try:
+ path = tempfile.mktemp(prefix=tempfile.template)
+ except TypeError:
+ path = tempfile.mktemp()
+ os.mkdir(path)
+
+ # Symlinks in the path will report things
+ # differently from os.getcwd(), so chdir there
+ # and back to fetch the canonical path.
+ cwd = os.getcwd()
+ try:
+ os.chdir(path)
+ path = os.getcwd()
+ finally:
+ os.chdir(cwd)
+
+ # Uppercase the drive letter since the case of drive
+ # letters is pretty much random on win32:
+ drive,rest = os.path.splitdrive(path)
+ if drive:
+ path = string.upper(drive) + rest
+
+ #
+ self._dirlist.append(path)
+ global _Cleanup
+ try:
+ _Cleanup.index(self)
+ except ValueError:
+ _Cleanup.append(self)
+
+ return path
+
def touch(self, path, mtime=None):
"""Updates the modification time on the specified file or
directory path name. The default is to update to the
"""
if (path != None):
if path == '':
- path = tempfile.mktemp()
- if path != None:
- os.mkdir(path)
- # We'd like to set self.workdir like this:
- # self.workdir = path
- # But symlinks in the path will report things
- # differently from os.getcwd(), so chdir there
- # and back to fetch the canonical path.
- cwd = os.getcwd()
- os.chdir(path)
- self.workdir = os.getcwd()
- os.chdir(cwd)
- # Uppercase the drive letter since the case of drive
- # letters is pretty much random on win32:
- drive,rest = os.path.splitdrive(self.workdir)
- if drive:
- self.workdir = string.upper(drive) + rest
- #
- self._dirlist.append(self.workdir)
- global _Cleanup
- try:
- _Cleanup.index(self)
- except ValueError:
- _Cleanup.append(self)
- else:
- self.workdir = None
+ path = None
+ path = self.tempdir(path)
+ self.workdir = path
def workpath(self, *args):
"""Returns the absolute path name to a subdirectory or file
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.23.D001 2006/11/30 13:57:29 knight"
-__version__ = "0.23"
+__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight"
+__version__ = "0.26"
import os
import os.path
except (SCons.Errors.UserError, SCons.Errors.InternalError):
return None
- def detect(self, var, prog=None, ENV=None):
+ def detect(self, var, prog=None, ENV=None, norm=None):
"""
Detect a program named 'prog' by first checking the construction
variable named 'var' and finally searching the path used by
prog = v
if v != prog:
return None
- return env.WhereIs(prog)
+ result = env.WhereIs(prog)
+ if norm and os.sep != '/':
+ result = string.replace(result, os.sep, '/')
+ return result
def detect_tool(self, tool, prog=None, ENV=None):
"""
checkpoint = 'r' + revision
version = version + checkpoint
+svn_status = None
+svn_status_lines = []
+
if svn:
svn_status = os.popen("%s status --verbose 2> /dev/null" % svn, "r").read()
svn_status_lines = svn_status[:-1].split('\n')
-else:
- svn_status_lines = []
build_id = ARGUMENTS.get('BUILD_ID')
if build_id is None:
"The default is whatever hostname is returned " +
"by socket.gethostname()."),
- ("CHECKPOINT=", "The specific checkpoint release being packaged. " +
- "This will be appended to the VERSION string. " +
+ ("CHECKPOINT=", "The specific checkpoint release being packaged, " +
+ "which will be appended to the VERSION string. " +
"A value of CHECKPOINT=d will generate a string " +
- "of 'd' plus today's date in the format YYYMMDD." +
+ "of 'd' plus today's date in the format YYYMMDD. " +
"A value of CHECKPOINT=r will generate a " +
- "string of 'r' plus the Subversion revision number. " +
- "Any other CHECKPOINT= string will be used as is." +
- "There is no default value."),
+ "string of 'r' plus the Subversion revision " +
+ "number. Any other CHECKPOINT= string will be " +
+ "used as is. There is no default value."),
("DATE=", "The date string representing when the packaging " +
"build occurred. The default is the day and time " +
indent_fmt = ' %-26s '
-Help("""
+Help("""\
The following aliases build packages of various types, and unpack the
contents into build/test-$PACKAGE subdirectories, which can be used by the
runtest.py -p option to run tests against what's been actually packaged:
--- /dev/null
+# __COPYRIGHT__
+#
+# Benchmarks for testing the selection of dependency changed functions
+# in src/engine/Environment.py.
+
+
+def use_a_dict(env, dep, arg):
+ func = {
+ '1111' : dep.func1,
+ '2222' : dep.func2,
+ '3333' : dep.func3,
+ '4444' : dep.func4,
+ }
+ t = env.get_type()
+ return func[t](arg)
+
+
+def use_if_tests(env, dep, arg):
+ t = env.get_type()
+ if t == '1111':
+ func = dep.func1
+ elif t == '2222':
+ func = dep.func2
+ elif t == '3333':
+ func = dep.func3
+ elif t == '4444':
+ func = dep.func4
+ else:
+ raise Exception, "bad key %s" % t
+ return func(arg)
+
+
+class Environment():
+ def __init__(self, t):
+ self.t = t
+ def get_type(self):
+ return self.t
+
+class Node():
+ def func1(self, arg):
+ pass
+ def func2(self, arg):
+ pass
+ def func3(self, arg):
+ pass
+ def func4(self, arg):
+ pass
+
+node = Node()
+
+def Func01(t):
+ """use_a_dict"""
+ env = Environment(t)
+ for i in IterationList:
+ use_a_dict(env, node, None)
+
+def Func02(t):
+ """use_if_tests"""
+ env = Environment(t)
+ for i in IterationList:
+ use_if_tests(env, node, None)
+
+
+
+# Data to pass to the functions on each run. Each entry is a
+# three-element tuple:
+#
+# (
+# "Label to print describing this data run",
+# ('positional', 'arguments'),
+# {'keyword' : 'arguments'},
+# ),
+
+class A:
+ pass
+
+Data = [
+ (
+ "1",
+ ('1111',),
+ {},
+ ),
+ (
+ "2",
+ ('2222',),
+ {},
+ ),
+ (
+ "3",
+ ('3333',),
+ {},
+ ),
+ (
+ "4",
+ ('4444',),
+ {},
+ ),
+]
.I function
itself is used for the method name.
+Examples:
+
.ES
# Note that the first argument to the function to
# be attached as a method must be the object through
alias to add additional targets to the alias,
or additional actions to the list for this alias.
+Examples:
+
.ES
Alias('install')
Alias('install', '/usr/bin')
is called multiple times,
each call completely overwrites the previous list
of allowed exceptions.
+
Example:
.ES
and the lists are added together.
(See also the Prepend method, below.)
+Example:
+
.ES
env.Append(CCFLAGS = ' -g', FOO = ['foo.yyy'])
.EE
This can also handle the
case where the given old path variable is a list instead of a
string, in which case a list will be returned instead of a string.
+
Example:
.ES
.I not
be added again to the list.
+Example:
+
.ES
env.AppendUnique(CCFLAGS = '-g', FOO = ['foo.yyy'])
.EE
.B SourceCode
function.
+Example:
+
.ES
env.SourceCode('.', env.BitKeeper())
.EE
.B \-
(hyphen)
to ignore the exit status of the external command.
+
Examples:
.ES
.BR Dir ()
or
.BR env.Dir ()
-functions:
+functions.
+
+Examples:
.ES
env.Command('ddd.list', Dir('ddd'), 'ls -l $SOURCE > $TARGET')
overwriting any existing values
for the keywords.
+Example:
+
.ES
env2 = env.Clone()
env3 = env.Clone(CCFLAGS = '-g')
.TP
.RI env.Copy([ key = val ", ...])"
A synonym for
-env.Clone().
+.BR env.Clone() .
(This will probably be officially deprecated some day.)
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
so that you only have to
replicate part of the repository
directory hierarchy in your
-local build directory:
+local build directory.
+
+Examples:
.ES
# Will fetch foo/bar/src.c
env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo/bar'))
.EE
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Decider( function )
+.TP
+.RI env.Decider( function )
+Specifies that all up-to-date decisions for
+targets built through this construction environment
+will be handled by the specified
+.IR function .
+The
+.I function
+can be one of the following strings
+that specify the type of decision function
+to be performed:
+
+.RS 10
+.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.
+This is the behavior of the classic Make utility,
+and
+.B make
+can be used a synonym for
+.BR timestamp-newer .
+
+.HP 6
+.B timestamp-match
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's timestamp is different than the
+timestamp recorded the last time the target was built.
+This provides behavior very similar to the classic Make utility
+(in particular, files are not opened up so that their
+contents can be checksummed)
+except that the target will also be rebuilt if a
+dependency file has been restored to a version with an
+.I earlier
+timestamp, such as can happen when restoring files from backup archives.
+
+.HP 6
+.B MD5
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's content has changed sine the last time
+the target was built,
+as determined be performing an MD5 checksum
+on the dependency's contents
+and comparing it to the checksum recorded the
+last time the target was built.
+.B content
+can be used as a synonym for
+.BR MD5 .
+
+.HP 6
+.B MD5-timestamp
+Specifies that a target shall be considered out of date and rebuilt
+if the dependency's content has changed sine the last time
+the target was built,
+except that dependencies with a timestamp that matches
+the last time the target was rebuilt will be
+assumed to be up-to-date and
+.I not
+rebuilt.
+This provides behavior very similar
+to the
+.B MD5
+behavior of always checksumming file contents,
+with an optimization of not checking
+the contents of files whose timestamps haven't changed.
+The drawback is that SCons will
+.I not
+detect if a file's content has changed
+but its timestamp is the same,
+as might happen in an automated script
+that runs a build,
+updates a file,
+and runs the build again,
+all within a single second.
+.RE
+
+Examples:
+
+.ES
+# Use exact timestamp matches by default.
+Decider('timestamp-match')
+
+# Use MD5 content signatures for any targets built
+# with the attached construction environment.
+env.Decider('content')
+.EE
+
+In addition to the above already-available functions,
+the
+.I function
+argument may be an actual Python function
+that takes the following three arguments:
+
+.IP dependency
+The Node (file) which
+should cause the
+.I target
+to be rebuilt
+if it has "changed" since the last tme
+.I target was built.
+
+.IP target
+The Node (file) being built.
+In the normal case,
+this is what should get rebuilt
+if the
+.I dependency
+has "changed."
+
+.IP prev_ni
+Stored information about the state of the
+.I dependency
+the last time the
+.I target
+was built.
+This can be consulted to match various
+file characteristics
+such as the timestamp,
+size, or content signature.
+
+The
+.I function
+should return a
+.B True
+(non-zero)
+value if the
+.I dependency
+has "changed" since the last time
+the
+.I target
+was built
+(indicating that the target
+.I should
+be rebuilt),
+and
+.B False
+(zero)
+otherwise
+(indicating that the target should
+.I not
+be rebuilt).
+Note that the decision can be made
+using whatever criteria are appopriate.
+Ignoring some or all of the function arguments
+is perfectly normal.
+
+Example:
+
+.ES
+def my_decider(dependency, target, prev_ni):
+ return not os.path.exists(str(target))
+
+env.Decider(my_decider)
+.EE
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI Default( targets )
will also accept the Node returned by any
of a construction environment's
builder methods.
+
Examples:
.ES
is not caught by a Scanner
for the file.
+Example:
+
.ES
env.Depends('foo', 'other-input-file-for-foo')
.EE
only the specified construction
variables are returned in the dictionary.
+Example:
+
.ES
dict = env.Dictionary()
cc_dict = env.Dictionary('CC', 'CCFLAGS', 'CCCOM')
print out an error message and exit SCons with a non-zero exit code if the
actual Python version is not late enough.
+Example:
+
.ES
EnsurePythonVersion(2,2)
.EE
print out an error message and exit SCons with a non-zero exit code if the
actual SCons version is not late enough.
+Examples:
+
.ES
EnsureSConsVersion(0,14)
as separate arguments or as a list. A dictionary can be used to map
variables to a different name when exported. Both local variables and
global variables can be exported.
+
Examples:
.ES
for files that exist in the filesytem, this function also searches for
derived files that have not yet been built.
+Example:
+
.ES
foo = env.FindFile('foo', ['dir1', 'dir2'])
.EE
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI FindInstalledFiles( )
+.TP
+.RI env.FindInstalledFiles( )
+Returns the list of targets setup by the
+.B Install()
+or
+.B InstallAs()
+builders.
+
+This function serves as a convenient method to select the contents of
+a Binary Package.
+
+Example:
+
+.ES
+Install( '/bin', [ 'executable_a', 'executable_b' ] )
+
+# will return the file node list
+# [ '/bin/executable_a', '/bin/executable_b' ]
+FindInstalledFiles()
+
+Install( '/lib', [ 'some_library' ] )
+
+# will return the file node list
+# [ '/bin/executable_a', '/bin/executable_b', '/lib/some_library' ]
+FindInstalledFiles()
+.EE
+
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI FindSourceFiles( node = '"."' )
+.TP
+.RI env.FindSourceFiles( node = '"."' )
+
+Returns the list of nodes which serve as the source of the built files.
+It does so by inspecting the dependency tree starting at the optional
+argument
+.B node
+which defaults to the '"."'-node. It will then return all leafs of
+.B node.
+These are all children which have no further children.
+
+This function is a convenient method to select the contents of a Source
+Package.
+
+Example:
+
+.ES
+Program( 'src/main_a.c' )
+Program( 'src/main_b.c' )
+Program( 'main_c.c' )
+
+# returns ['main_c.c', 'src/main_a.c', 'SConstruct', 'src/main_b.c']
+FindSourceFiles()
+
+# returns ['src/main_b.c', 'src/main_a.c' ]
+FindSourceFiles( 'src' )
+.EE
+
+As you can see build support files (SConstruct in the above example)
+will also be returned by this function.
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI FindPathDirs( variable )
other Builders will automatically
flatten lists specified as input,
but direct Python manipulation of
-these lists does not:
+these lists does not.
+
+Examples:
.ES
foo = Object('foo.c')
will be ignored when deciding if
the target file(s) need to be rebuilt.
+Examples:
+
.ES
env.Ignore('foo', 'foo.c')
env.Ignore('bar', ['bar1.h', 'bar2.h'])
.BR Import ()
as separate arguments or as a list. The variable "*" can be used
to import all variables.
+
Examples:
.ES
The returned Builder
is intended to be passed to the
.B SourceCode
-function:
+function.
+
+Example:
.ES
env.SourceCode('.', env.Perforce())
Returns a callable object
that can be used to initialize
a construction environment using the
-platform keyword of the Environment() method:
+platform keyword of the Environment() method.
+
+Example:
.ES
env = Environment(platform = Platform('win32'))
.BR :pserver:anonymous@cvs.sourceforge.net:/cvsroot/scons )
will work on Windows systems.
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Progress( callable ", [" interval ])
+.TP
+.RI Progress( string ", [" interval ", " file ", " overwrite ])
+.TP
+.RI Progress( list_of_strings ", [" interval ", " file ", " overwrite ])
+Allows SCons to show progress made during the build
+by displaying a string or calling a function while
+evaluating Nodes (e.g. files).
+
+If the first specified argument is a Python callable
+(a function or an object that has a
+.BR __call__ ()
+method),
+the function will be called
+once every
+.I interval
+times a Node is evaluated.
+The callable will be passed the evaluated Node
+as its only argument.
+(For future compatibility,
+it's a good idea to also add
+.B *args
+and
+.B **kw
+as arguments to your function or method.
+This will prevent the code from breaking
+if SCons ever changes the interface
+to call the function with additional arguments in the future.)
+
+An example of a simple custom progress function
+that prints a string containing the Node name
+every 10 Nodes:
+
+.ES
+def my_progress_function(node, *args, **kw):
+ print 'Evaluating node %s!' % node
+Progress(my_progress_function, interval=10)
+.EE
+.IP
+A more complicated example of a custom progress display object
+that prints a string containing a count
+every 100 evaluated Nodes.
+Note the use of
+.B \\\\r
+(a carriage return)
+at the end so that the string
+will overwrite itself on a display:
+
+.ES
+import sys
+class ProgressCounter:
+ count = 0
+ def __call__(self, node, *args, **kw):
+ self.count += 100
+ sys.stderr.write('Evaluated %s nodes\\r' % self.count)
+Progress(ProgressCounter(), interval=100)
+.EE
+.IP
+If the first argument
+.BR Progress ()
+is a string,
+the string will be displayed
+every
+.I interval
+evaluated Nodes.
+The default is to print the string on standard output;
+an alternate output stream
+may be specified with the
+.B file=
+argument.
+The following will print a series of dots
+on the error output,
+one dot for every 100 evaluated Nodes:
+
+.ES
+import sys
+Progress('.', interval=100, file=sys.stderr)
+.EE
+.IP
+If the string contains the verbatim substring
+.B $TARGET,
+it will be replaced with the Node.
+Note that, for performance reasons, this is
+.I not
+a regular SCons variable substition,
+so you can not use other variables
+or use curly braces.
+The following example will print the name of
+every evaluated Node,
+using a
+.B \\\\r
+(carriage return) to cause each line to overwritten by the next line,
+and the
+.B overwrite=
+keyword argument to make sure the previously-printed
+file name is overwritten with blank spaces:
+
+.ES
+import sys
+Progress('$TARGET\\r', overwrite=True)
+.EE
+.IP
+If the first argument to
+.BR Progress ()
+is a list of strings,
+then each string in the list will be displayed
+in rotating fashion every
+.I interval
+evaluated Nodes.
+This can be used to implement a "spinner"
+on the user's screen as follows:
+
+.ES
+Progress(['-\\r', '\\\\\\r', '|\\r', '/\\r'], interval=5)
+.EE
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI Precious( target ", ...)"
and the lists are added together.
(See also the Append method, above.)
+Example:
+
.ES
env.Prepend(CCFLAGS = '-g ', FOO = ['foo.yyy'])
.EE
This can also handle the
case where the given old path variable is a list instead of a
string, in which case a list will be returned instead of a string.
+
Example:
.ES
include_path = '/foo/bar:/foo'
env.PrependENVPath('INCLUDE', include_path)
print 'after:',env['ENV']['INCLUDE']
+.EE
-yields:
+The above exmaple will print:
+
+.ES
before: /biz:/foo
after: /foo/bar:/foo:/biz
.EE
.I not
be added again to the list.
+Example:
+
.ES
env.PrependUnique(CCFLAGS = '-g', FOO = ['foo.yyy'])
.EE
.B SourceCode
function:
+Examples:
+
.ES
env.SourceCode('.', env.RCS())
.EE
Replaces construction variables in the Environment
with the specified keyword arguments.
+Example:
+
.ES
env.Replace(CCFLAGS = '-g', FOO = 'foo.xxx')
.EE
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
-.RI Return( vars )
-This tells
-.B scons
-what variable(s) to use as the return value(s) of the current SConscript
-file. These variables will be returned to the "calling" SConscript file
-as the return value(s) of
-.BR SConscript ().
-Multiple variable names should be passed to
+.RI Return([ vars "... , " stop= ])
+By default,
+this stops processing the current SConscript
+file and returns to the calling SConscript file
+the values of the variables named in the
+.I vars
+string arguments.
+Multiple strings contaning variable names may be passed to
+.BR Return ().
+Any strings that contain white space
+
+The optional
+.B stop=
+keyword argument may be set to a false value
+to continue processing the rest of the SConscript
+file after the
.BR Return ()
-as a list. Example:
+call.
+This was the default behavior prior to SCons 0.98.
+However, the values returned
+are still the values of the variables in the named
+.I vars
+at the point
+.BR Return ()
+is called.
+
+Examples:
.ES
+# Returns without returning a value.
+Return()
+
+# Returns the value of the 'foo' Python variable.
Return("foo")
-Return(["foo", "bar"])
+
+# Returns the values of the Python variables 'foo' and 'bar'.
+Return("foo", "bar")
+
+# Returns the values of Python variables 'val1' and 'val2'.
+Return('val1 val2')
.EE
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
The returned Builder
is intended to be passed to the
.B SourceCode
-function:
+function.
+
+Example:
.ES
env.SourceCode('.', env.SCCS())
(This may be necessary when building from repositories,
when all the directories in which SConscript files may be found
don't necessarily exist locally.)
-
You may enable and disable
this ability by calling
SConscriptChdir()
-multiple times:
+multiple times.
+
+Example:
.ES
env = Environment()
which corresponds to --random.
See the documentation for the
corresponding command line object for information about each specific
-option. Example:
+option.
+
+Example:
.ES
SetOption('max_drift', 1)
idiom to pass in an unnamed function
that simply returns its unconverted argument.
+Example:
+
.ES
print env.subst("The C compiler is: $CC")
'\"so that you only have to
'\"replicate part of the repository
'\"directory hierarchy in your
-'\"local build directory:
+'\"local build directory.
+'\"
+'\"Example:
'\"
'\".ES
'\"# Will fetch foo/bar/src.c
.RI SourceSignatures( type )
.TP
.RI env.SourceSignatures( type )
-This function tells SCons what type of signature to use for source files:
+This function tells
+.B scons
+how to decide if a source file
+(a file that is not built from any other files)
+has changed since the last time it
+was used to build a particular target file.
+Legal values are
.B "MD5"
or
.BR "timestamp" .
+
If the environment method is used,
the specified type of source signature
is only used when deciding whether targets
used for all decisions
about whether targets are up-to-date.
-"MD5" means the signature of a source file
-is the MD5 checksum of its contents.
-"timestamp" means the signature of a source file
-is its timestamp (modification time).
+.B "MD5"
+means
+.B scons
+decides that a source file has changed
+if the MD5 checksum of its contents has changed since
+the last time it was used to rebuild a particular target file.
+
+.B "timestamp"
+means
+.B scons
+decides that a source file has changed
+if its timestamp (modification time) is newer than
+the last time it was used to rebuild a particular target file.
+
There is no different between the two behaviors
for Python
.BR Value ()
node objects.
-"MD5" signatures take longer to compute,
-but are more accurate than "timestamp" signatures.
-The default is "MD5".
+
+.B "MD5"
+signatures take longer to compute,
+but are more accurate than
+.B "timestamp"
+signatures.
+The default value is
+.BR "MD5" .
+
+Note that the default
+.BR TargetSignatures ()
+setting (see below)
+is to use this
+.BR SourceSignatures ()
+setting for any target files that are used
+to build other target files.
+Consequently, changing the value of
+.BR SourceSignatures ()
+will, by default,
+affect the up-to-date decision for all files in the build
+(or all files built with a specific construction environment
+when
+.BR env.SourceSignatures ()
+is used).
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
it will be returned as a list
containing just the object.
+Example:
+
.ES
files = Split("f1.c f2.c f3.c")
files = env.Split("f4.c f5.c f6.c")
Builder should package those files or directories.
All tags are optional.
+Examples:
+
.ES
# makes sure the built library will be installed with 0644 file
# access mode
.RI TargetSignatures( type )
.TP
.RI env.TargetSignatures( type )
-This function tells SCons what type of signatures to use
-for target files:
-.B "build"
+This function tells
+.B scons
+how to decide if a target file
+(a file that
+.I is
+built from any other files)
+has changed since the last time it
+was used to build some other target file.
+Legal values are
+.BR "build" ;
+.BR "content"
+(or its synonym
+.BR "MD5" );
+.BR "timestamp" ;
or
-.BR "content" .
+.BR "source" .
+
If the environment method is used,
-the specified type of signature is only used
+the specified type of target signature is only used
for targets built with that environment.
If the global function is used,
the specified type of signature becomes the default
don't have an explicit target signature type
specified for their environments.
-"build" means the signature of a target file
-is made by concatenating all of the
-signatures of all its source files.
-"content" means the signature of a target
-file is an MD5 checksum of its contents.
-"build" signatures are usually faster to compute,
-but "content" signatures can prevent unnecessary rebuilds
+.B "content"
+(or its synonym
+.BR "MD5" )
+means
+.B scons
+decides that a target file has changed
+if the MD5 checksum of its contents has changed since
+the last time it was used to rebuild some other target file.
+This means
+.B scons
+will open up
+MD5 sum the contents
+of target files after they're built,
+and may decide that it does not need to rebuild
+"downstream" target files if a file was
+rebuilt with exactly the same contents as the last time.
+
+.B "timestamp"
+means
+.B scons
+decides that a target file has changed
+if its timestamp (modification time) is newer than
+the last time it was used to rebuild some other target file.
+
+.B "source"
+means
+.B scons
+decides that a target file has changed
+as specified by the corresponding
+.BR SourceSignatures ()
+setting
+.BR "" ( "MD5"
+or
+.BR "timestamp" ).
+This means that
+.B scons
+will treat all input files to a target the same way,
+regardless of whether they are source files
+or have been built from other files.
+
+.B "build"
+means
+.B scons
+decides that a target file has changed
+if it has been rebuilt in this invocation
+or if its content or timestamp have changed
+as specified by the corresponding
+.BR SourceSignatures ()
+setting.
+This "propagates" the status of a rebuilt file
+so that other "downstream" target files
+will always be rebuilt,
+even if the contents or the timestamp
+have not changed.
+
+.B "build"
+signatures are fastest because
+.B "content"
+(or
+.BR "MD5" )
+signatures take longer to compute,
+but are more accurate than
+.B "timestamp"
+signatures,
+and can prevent unnecessary "downstream" rebuilds
when a target file is rebuilt to the exact same contents
as the previous build.
-The default is "build".
+The
+.B "source"
+setting provides the most consistent behavior
+when other target files may be rebuilt from
+both source and target input files.
+The default value is
+.BR "source" .
+
+Because the default setting is
+.BR "source" ,
+using
+.BR SourceSignatures ()
+is generally preferable to
+.BR TargetSignatures () ,
+so that the up-to-date decision
+will be consistent for all files
+(or all files built with a specific construction environment).
+Use of
+.BR TargetSignatures ()
+provides specific control for how built target files
+affect their "downstream" dependencies.
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.B generate()
method.
+Examples:
+
.ES
env = Environment(tools = [ Tool('msvc') ])
.BR str( value )
changes between SCons runs, any targets depending on
.BR Value( value )
-will be rebuilt. When using timestamp source signatures, Value Nodes'
+will be rebuilt.
+(This is true even when using timestamps to decide if
+files are up-to-date.)
+When using timestamp source signatures, Value Nodes'
timestamps are equal to the system time when the Node is created.
The returned Value Node object has a
.BR read ()
method that will return the built value of the Node.
+Examples:
+
.ES
+env = Environment()
+
def create(target, source, env):
+ # A function that will write a 'prefix=$SOURCE'
+ # string into the file name specified as the
+ # $TARGET.
f = open(str(target[0]), 'wb')
f.write('prefix=' + source[0].get_contents())
+# Fetch the prefix= argument, if any, from the command
+# line, and use /usr/local as the default.
prefix = ARGUMENTS.get('prefix', '/usr/local')
-env = Environment()
+
+# Attach a .Config() builder for the above function action
+# to the construction environment.
env['BUILDERS']['Config'] = Builder(action = create)
env.Config(target = 'package-config', source = Value(prefix))
def build_value(target, source, env):
+ # A function that "builds" a Python Value by updating
+ # the the Python value with the contents of the file
+ # specified as the source of the Builder call ($SOURCE).
target[0].write(source[0].get_contents())
output = env.Value('before')
input = env.Value('after')
+# Attach a .UpdateValue() builder for the above function
+# action to the construction environment.
env['BUILDERS']['UpdateValue'] = Builder(action = build_value)
env.UpdateValue(target = Value(output), source = Value(input))
.EE
.B [1]
of the tuple, respectively.
+Example:
+
.ES
print "first keyword, value =", ARGLIST[0][0], ARGLIST[0][1]
print "second keyword, value =", ARGLIST[1][0], ARGLIST[1][1]
.B ARGUMENTS
dictionary.
+Example:
+
.ES
if ARGUMENTS.get('debug', 0):
env = Environment(CCFLAGS = '-g')
list, below,
for additional information.
+Example:
+
.ES
if 'foo' in BUILD_TARGETS:
print "Don't forget to test the `foo' program!"
This can be used, for example,
to take specific actions only
when a certain target or targets
-is explicitly being built:
+is explicitly being built.
+
+Example:
.ES
if 'foo' in COMMAND_LINE_TARGETS:
.B str
function to get at the path name for each Node.
+Example:
+
.ES
print str(DEFAULT_TARGETS[0])
if 'foo' in map(str, DEFAULT_TARGETS):
"$SRC_SFX_A": gen_suffix })
.EE
+.IP ensure_suffix
+When set to any true value, causes
+.B scons
+to add the target suffix specified by the
+.I suffix
+keyword to any target strings
+that have a different suffix.
+(The default behavior is to leave untouched
+any target file name that looks like it already has any suffix.)
+
+.ES
+b1 = Builder("build_it < $SOURCE > $TARGET"
+ suffix = ".out")
+b2 = Builder("build_it < $SOURCE > $TARGET"
+ suffix = ".out",
+ ensure_suffix)
+env = Environment()
+env['BUILDERS']['B1'] = b1
+env['BUILDERS']['B2'] = b2
+
+# Builds "foo.txt" because ensure_suffix is not set.
+env.B1('foo.txt', 'foo.in')
+
+# Builds "bar.txt.out" because ensure_suffix is set.
+env.B2('bar.txt', 'bar.in')
+.EE
+
.IP src_suffix
The expected source file name suffix. This may be a string or a list
of strings.
specified file(s).
Each entry is printed in the following format:
- file: timestamp bsig csig
- implicit_dependency_1
- implicit_dependency_2
+ file: signature timestamp length
+ implicit_dependency_1: signature timestamp length
+ implicit_dependency_2: signature timestamp length
+ action_signature [action string]
-If the entry has no timestamp, bsig, or csig, a dash
.B None
-is printed.
+is printed
+in place of any missing timestamp, bsig, or csig
+values for
+any entry
+or any of its dependencies.
If the entry has no implicit dependencies,
+or no build action,
the lines are simply omitted.
By default,
and the format:
.TP
--b, --bsig
-Prints the build signature (bsig) information
+-a, --act, --action
+Prints the build action information
for all entries or the specified entries.
.TP
--raw
Prints a pretty-printed representation
of the raw Python dictionary that holds
-build information about an entry.
+build information about individual entry
+(both the entry itself or its implicit dependencies).
+An entry's build action is still printed in its usual format.
.TP
-r, --readable
def escape(s):
if _ws.search(s):
s = '"' + s + '"'
+ s = string.replace(s, '\\', '\\\\')
return s
# Set up lowest-common-denominator spawning of a process on both Windows
- Allow env.CacheDir() to be set per construction environment. The
global CacheDir() function now sets an overridable global default.
+ - Add an env.Decider() method and a Node.Decider() method that allow
+ flexible specification of an arbitrary function to decide if a given
+ dependency has changed since the last time a target was built.
+
+ - Don't execute Configure actions (while reading SConscript files)
+ when cleaning (-c) or getting help (-h or -H).
+
+ - Add to each target an implicit dependency on the external command(s)
+ used to build the target, as found by searching env['ENV']['PATH']
+ for the first argument on each executed command line.
+
+ - Add support for a $IMPLICIT_COMMAND_DEPENDENCIES construction
+ variabe that can be used to disable the automatic implicit
+ dependency on executed commands.
+
+ - Add an "ensure_suffix" keyword to Builder() definitions that, when
+ true, will add the configured suffix to the targets even if it looks
+ like they already have a different suffix.
+
+ - Add a Progress() function that allows for calling a function or string
+ (or list of strings) to display progress while walking the DAG.
+
+ - Allow ParseConfig(), MergeFlags() and ParseFlags() to handle output
+ from a *config command with quoted path names that contain spaces.
+
+ - Make the Return() function stop processing the SConscript file and
+ return immediately. Add a "stop=" keyword argument that can be set
+ to False to preserve the old behavior.
+
+ - Fix use of exitstatfunc on an Action.
+
+ - Introduce all man page function examples with "Example:" or "Examples:".
+
+ - When a file gets added to a directory, make sure the directory gets
+ re-scanned for the new implicit dependency.
+
+ - Fix handling a file that's specified multiple times in a target
+ list so that it doesn't cause dependent Nodes to "disappear" from
+ the dependency graph walk.
+
+ From Carsten Koch:
+
+ - Avoid race conditions with same-named files and directory creation
+ when pushing copies of files to CacheDir().
+
+ From Tzvetan Mikov:
+
+ - Handle $ in Java class names.
+
+ From Gary Oberbrunner:
+
+ - Add support for the Intel C compiler on Windows64.
+
+ - On SGI IRIX, have $SHCXX use $CXX by default (like other platforms).
+
+ From Sohail Somani:
+
+ - When Cloning a construction environment, set any variables before
+ applying tools (so the tool module can access the configured settings)
+ and re-set them after (so they end up matching what the user set).
+
+ From Matthias Troffaes:
+
+ - Make sure extra auxiliary files generated by some LaTeX packages
+ and not ending in .aux also get deleted by scons -c.
+
+ From Greg Ward:
+
+ - Add a $JAVABOOTCLASSPATH variable for directories to be passed to the
+ javac -bootclasspath option.
+
+ From Christoph Wiedemann:
+
+ - Add implicit dependencies on the commands used to build a target.
+
+
RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500
-RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500
+RELEASE 0.97.0d200709XX - XXX
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.0d20070809:
+
+ -- "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR
+
+ The default behavior of SCons is now to use the MD5 checksum of
+ all file contents to decide if any files have changed and should
+ cause rebuilds of their source files. This means that SCons may
+ decide not to rebuild "downstream" targets if a a given input
+ file is rebuilt to the exact same contents as the last time.
+ The old behavior may preserved by explicity specifying:
+
+ TargetSignatures("build")
+
+ In any of your SConscript files.
+
+ -- TARGETS NOW IMPLICITLY DEPEND ON THE COMMAND THAT BUILDS THEM
+
+ For all targets built by calling external commands (such as a
+ compiler or other utility), SCons now adds an implicit dependency
+ on the command(s) used to build the target.
+
+ This will cause rebuilds of all targets built by external commands
+ when running SCons in a tree built by previous version of SCons,
+ in order to update the recorded signatures.
+
+ The old behavior of not having targets depend on the external
+ commands that build them can be preserved by setting a new
+ $IMPLICIT_COMMAND_DEPENDENCIES construction variable to a
+ non-True value:
+
+ env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+
+ or by adding Ignore() calls for any targets where the behavior
+ is desired:
+
+ Ignore('/usr/bin/gcc', 'foo.o')
+
+ Both of these settings are compatible with older versions
+ of SCons.
+
+ -- CHANGING SourceSignature() MAY CAUSE "UNECESSARY" REBUILDS
+
+ If you change the SourceSignature() value from 'timestamp' to
+ 'MD5', SCons will now rebuild targets that were already up-to-date
+ with respect to their source files.
+
+ This will happen because SCons did not record the content
+ signatures of the input source files when the target was last
+ built--it only recorded the timestamps--and it must record them
+ to make sure the signature information is correct. However,
+ the content of source files may have changed since the last
+ timestamp build was performed, and SCons would not have any way to
+ verify that. (It would have had to open up the file and record
+ a content signature, which is one of the things you're trying to
+ avoid by specifying use of timestamps....) So in order to make
+ sure the built targets reflect the contents of the source files,
+ the targets must be rebuilt.
+
+ Change the SourceSignature() value from 'MD5' to 'timestamp'
+ should correctly not rebuild target files, because the timestamp
+ of the files is always recorded.
+
+ In previous versions of SCons, changing the SourceSignature()
+ value would lead to unpredictable behavior, usually including
+ rebuilding targets.
+
+ -- THE Return() FUNCTION NOW ACTUALLY RETURNS IMMEDIATELY
+
+ The Return() function now immediately stops processing the
+ SConscript file in which it appears and returns the values of the
+ variables named in its arguments. It used to continue processing
+ the rest of the SConscript file, and then return the values of the
+ specified variables at the point the Return() function was called.
+
+ The old behavior may be requested by adding a "stop=False"
+ keyword argument to the Return() call:
+
+ Return('value', stop=False)
+
+ The "stop=" keyword argument is *not* compatible with SCons
+ versions 0.97.0d20070809 or earlier.
+
Please note the following important changes since release 0.97:
-- env.CacheDir() NOW ONLY AFFECTS CONSTRUCTION ENVIRONMENT TARGETS
This should not cause any problems in the normal use of "#ifdef
HAVE_{FEATURE}" statements interpreted by a C preprocessor, but
might cause a compatibility issue if a script or other utility
- was looking for an exact match of the previous text.
+ looks for an exact match of the previous text.
Please note the following important changes since release 0.96.93:
SCons/Action.py
SCons/Builder.py
SCons/compat/__init__.py
+SCons/compat/_scons_hashlib.py
SCons/compat/_scons_optparse.py
SCons/compat/_scons_sets.py
SCons/compat/_scons_sets15.py
SCons/Script/Main.py
SCons/Script/SConscript.py
SCons/Script/SConsOptions.py
-SCons/Sig/__init__.py
-SCons/Sig/MD5.py
-SCons/Sig/TimeStamp.py
+SCons/Sig.py
SCons/Subst.py
SCons/Taskmaster.py
SCons/Tool/__init__.py
SCons/Tool/packaging/__init__.py
SCons/Tool/packaging/ipk.py
SCons/Tool/packaging/msi.py
-SCons/Tool/packaging/packager.py
SCons/Tool/packaging/rpm.py
SCons/Tool/packaging/src_tarbz2.py
SCons/Tool/packaging/src_targz.py
get_contents()
Fetches the "contents" of an Action for signature calculation.
- This is what the Sig/*.py subsystem uses to decide if a target
- needs to be rebuilt because its action changed.
+ This is what gets MD5 checksumm'ed to decide if a target needs
+ to be rebuilt because its action changed.
genstring()
Returns a string representation of the Action *without*
cmd = str(cmd)
return env.subst_target_source(cmd, SUBST_SIG, target, source)
+ def get_implicit_deps(self, target, source, env):
+ icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
+ if SCons.Util.is_String(icd) and icd[:1] == '$':
+ icd = env.subst(icd)
+ if not icd or icd in ('0', 'None'):
+ return []
+ from SCons.Subst import SUBST_SIG
+ cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source)
+ res = []
+ for cmd_line in cmd_list:
+ if cmd_line:
+ d = env.WhereIs(str(cmd_line[0]))
+ if d:
+ res.append(env.fs.File(d))
+ return res
+
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
def __init__(self, generator, *args, **kw):
"""
return self._generate(target, source, env, 1).get_contents(target, source, env)
+ def get_implicit_deps(self, target, source, env):
+ return self._generate(target, source, env, 1).get_implicit_deps(target, source, env)
+
# A LazyAction is a kind of hybrid generator and command action for
return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
self.varlist)))
+ def get_implicit_deps(self, target, source, env):
+ return []
+
class ListAction(ActionBase):
"""Class for lists of other actions."""
def __init__(self, list):
return stat
return 0
+ def get_implicit_deps(self, target, source, env):
+ result = []
+ for act in self.list:
+ result.extend(act.get_implicit_deps(target, source, env))
+ return result
+
class ActionCaller:
"""A class for delaying calling an Action function with specific
(positional and keyword) arguments until the Action is actually
This file is processed by the bin/SConsDoc.py module.
See its __doc__ string for a discussion of the format.
-->
+<cvar name="IMPLICIT_COMMAND_DEPENDENCIES">
+<summary>
+Controls whether or not SCons will
+add implicit dependencies for the commands
+executed to build targets.
+
+By default, SCons will add
+to each target
+an implicit dependency on the command
+represented by the first argument on any
+command line it executes.
+The specific file for the dependency is
+found by searching the
+<varname>PATH</varname>
+variable in the
+<varname>ENV</varname>
+environment used to execute the command.
+
+If the construction variable
+&cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is set to a false value
+(<literal>None</literal>,
+<literal>False</literal>,
+<literal>0</literal>,
+etc.),
+then the implicit dependency will
+not be added to the targets
+built with that construction environment.
+
+<example>
+env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+</example>
+</summary>
+</cvar>
+
<cvar name="PRINT_CMD_LINE_FUNC">
<summary>
A Python function used to print the command lines as they are executed
variable. This also takes care of warning about possible mistakes
in keyword arguments.
- targets()
- Returns the list of targets for a specific builder instance.
-
add_emitter()
Adds an emitter for a specific file suffix, used by some Tool
modules to specify that (for example) a yacc invocation on a .y
chdir = _null,
is_explicit = 1,
src_builder = [],
+ ensure_suffix = False,
**overrides):
if __debug__: logInstanceCreation(self, 'Builder.BuilderBase')
self._memo = {}
self.set_suffix(suffix)
self.set_src_suffix(src_suffix)
+ self.ensure_suffix = ensure_suffix
self.target_factory = target_factory
self.source_factory = source_factory
executor.add_sources(slist)
return executor
- def _adjustixes(self, files, pre, suf):
+ def _adjustixes(self, files, pre, suf, ensure_suffix=False):
if not files:
return []
result = []
for f in files:
if SCons.Util.is_String(f):
- f = SCons.Util.adjustixes(f, pre, suf)
+ f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
result.append(f)
return result
splitext = lambda S,self=self,env=env: self.splitext(S,env)
tlist = [ t_from_s(pre, suf, splitext) ]
else:
- target = self._adjustixes(target, pre, suf)
+ target = self._adjustixes(target, pre, suf, self.ensure_suffix)
tlist = env.arg2nodes(target, target_factory)
if self.emitter:
return ''
return ret[0]
- def targets(self, node):
- """Return the list of targets for this builder instance.
-
- For most normal builders, this is just the supplied node.
- """
- return [ node ]
-
def add_emitter(self, suffix, emitter):
"""Add a suffix-emitter mapping to this Builder.
"""
self.emitter[suffix] = emitter
- def push_emitter(self, emitter):
- """Add a emitter to the beginning of the emitter list of this Builder.
-
- This creates an empty list if the emitter is None.
- """
- if not self.emitter:
- self.emitter = ListEmitter( [emitter] )
- else:
- self.emitter.insert(0, emitter)
-
def add_src_builder(self, builder):
"""
Add a new Builder to the list of src_builders.
cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
+ tempfile = cachefile+'.tmp'+str(os.getpid())
+ errfmt = "Unable to copy %s to cache. Cache file is %s"
+
if not fs.isdir(cachedir):
- fs.makedirs(cachedir)
+ try:
+ fs.makedirs(cachedir)
+ except EnvironmentError:
+ # We may have received an exception because another process
+ # has beaten us creating the directory.
+ if not fs.isdir(cachedir):
+ msg = errfmt % (str(target), cachefile)
+ raise SCons.Errors.EnvironmentError, msg
- tempfile = cachefile+'.tmp'
try:
if fs.islink(t.path):
fs.symlink(fs.readlink(t.path), tempfile)
fs.rename(tempfile, cachefile)
st = fs.stat(t.path)
fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
- except (IOError, OSError):
+ except EnvironmentError:
# It's possible someone else tried writing the file at the
# same time we did, or else that there was some problem like
# the CacheDir being on a separate file system that's full.
# In any case, inability to push a file to cache doesn't affect
# the correctness of the build, so just print a warning.
- SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning,
- "Unable to copy %s to cache. Cache file is %s"
- % (str(target), cachefile))
+ msg = errfmt % (str(target), cachefile)
+ SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
CachePush = SCons.Action.Action(CachePushFunc, None)
def __init__(self, path):
try:
- import SCons.Sig.MD5
+ import hashlib
except ImportError:
- msg = "No MD5 module available, CacheDir() not supported"
+ msg = "No hashlib or MD5 module available, CacheDir() not supported"
SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
else:
self.path = path
class Null(SCons.Util.Null):
def repr(self):
return 'CacheDir.Null()'
+ def cachepath(self, node):
+ return None, None
def retrieve(self, node):
return False
built_it = None
class Action:
- def __call__(self, targets, sources, env, errfunc, **kw):
+ def __call__(self, targets, sources, env, **kw):
global built_it
if kw.get('execute', 1):
built_it = 1
return 0
+ def genstring(self, target, source, env):
+ return str(self)
+ def get_contents(self, target, source, env):
+ return ''
class Builder:
def __init__(self, environment, action):
self.env = environment
self.action = action
self.overrides = {}
+ self.source_scanner = None
+ self.target_scanner = None
class Environment:
def __init__(self, cachedir):
node = self.fs.File(name)
node.builder_set(Builder(Environment(self._CacheDir), action))
if bsig:
- node.binfo = node.BuildInfo(node)
- node.binfo.ninfo.bsig = bsig
+ node.cachesig = bsig
+ #node.binfo = node.BuildInfo(node)
+ #node.binfo.ninfo.bsig = bsig
return node
class CacheDirTestCase(BaseTestCase):
# of the file in cache.
def my_collect(list):
return list[0]
- save_collect = SCons.Sig.MD5.collect
- SCons.Sig.MD5.collect = my_collect
+ save_collect = SCons.Util.MD5collect
+ SCons.Util.MD5collect = my_collect
try:
f5 = self.File("cd.f5", 'a_fake_bsig')
filename = os.path.join(dirname, 'a_fake_bsig')
assert result == (dirname, filename), result
finally:
- SCons.Sig.MD5.collect = save_collect
+ SCons.Util.MD5collect = save_collect
class FileTestCase(BaseTestCase):
"""
finally:
SCons.CacheDir.CachePush = save_CachePush
- def test_no_bsig(self):
- """Test that no bsig raises an InternalError"""
-
- f6 = self.File("cd.f6")
- f6.binfo = f6.BuildInfo(f6)
- exc_caught = 0
- try:
- cp = self._CacheDir.cachepath(f6)
- except SCons.Errors.InternalError:
- exc_caught = 1
- assert exc_caught
-
def test_warning(self):
"""Test raising a warning if we can't copy a file to cache."""
# Assume we were passed an open file pointer.
fp = file
fp.write(msg)
+ fp.flush()
import SCons.CacheDir
import SCons.Environment
import SCons.PathList
-import SCons.Sig
import SCons.Subst
import SCons.Tool
# Lazily instantiate the default environment so the overhead of creating
# it doesn't apply when it's not needed.
+def _fetch_DefaultEnvironment(*args, **kw):
+ """
+ Returns the already-created default construction environment.
+ """
+ global _default_env
+ return _default_env
+
def DefaultEnvironment(*args, **kw):
+ """
+ Initial public entry point for creating the default construction
+ Environment.
+
+ After creating the environment, we overwrite our name
+ (DefaultEnvironment) with the _fetch_DefaultEnvironment() function,
+ which more efficiently returns the initialized default construction
+ environment without checking for its existence.
+
+ (This function still exists with its _default_check because someone
+ else (*cough* Script/__init__.py *cough*) may keep a reference
+ to this function. So we can't use the fully functional idiom of
+ having the name originally be a something that *only* creates the
+ construction environment and then overwrites the name.)
+ """
global _default_env
if not _default_env:
+ import SCons.Util
_default_env = apply(SCons.Environment.Environment, args, kw)
- _default_env._build_signature = 1
- _default_env._calc_module = SCons.Sig.default_module
+ _default_env.TargetSignatures('source')
+ if SCons.Util.md5:
+ _default_env.SourceSignatures('MD5')
+ else:
+ _default_env.SourceSignatures('timestamp')
+ global DefaultEnvironment
+ DefaultEnvironment = _fetch_DefaultEnvironment
_default_env._CacheDir = SCons.CacheDir.Null()
return _default_env
ObjSourceScan = SCons.Tool.SourceFileScanner
ProgScan = SCons.Tool.ProgramScanner
-# This isn't really a tool scanner, so it doesn't quite belong with
-# the rest of those in Tool/__init__.py, but I'm not sure where else it
-# should go. Leave it here for now.
+# These aren't really tool scanners, so they don't quite belong with
+# the rest of those in Tool/__init__.py, but I'm not sure where else
+# they should go. Leave them here for now.
import SCons.Scanner.Dir
DirScanner = SCons.Scanner.Dir.DirScanner()
DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner()
import copy
import os
import os.path
+import shlex
import string
from UserDict import UserDict
import SCons.Node.Python
import SCons.Platform
import SCons.SConsign
-import SCons.Sig
import SCons.Subst
import SCons.Tool
import SCons.Util
for k in bd.keys():
del bd[k]
except KeyError:
- env._dict[key] = BuilderDict(kwbd, env)
- env._dict[key].update(value)
+ bd = BuilderDict(kwbd, env)
+ env._dict[key] = bd
+ bd.update(value)
def _del_SCANNERS(env, key):
del env._dict[key]
env._dict[key] = value
env.scanner_map_delete()
-class BuilderWrapper:
- """Wrapper class that associates an environment with a Builder at
- instantiation."""
- def __init__(self, env, builder):
- self.env = env
- self.builder = builder
+
+# The following is partly based on code in a comment added by Peter
+# Shannon at the following page (there called the "transplant" class):
+#
+# ASPN : Python Cookbook : Dynamically added methods to a class
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732
+#
+# We had independently been using the idiom as BuilderWrapper, but
+# factoring out the common parts into this base class, and making
+# BuilderWrapper a subclass that overrides __call__() to enforce specific
+# Builder calling conventions, simplified some of our higher-layer code.
+
+class MethodWrapper:
+ """
+ A generic Wrapper class that associates a method (which can
+ actually be any callable) with an object. As part of creating this
+ MethodWrapper object an attribute with the specified (by default,
+ the name of the supplied method) is added to the underlying object.
+ When that new "method" is called, our __call__() method adds the
+ object as the first argument, simulating the Python behavior of
+ supplying "self" on method calls.
+
+ We hang on to the name by which the method was added to the underlying
+ base class so that we can provide a method to "clone" ourselves onto
+ a new underlying object being copied (without which we wouldn't need
+ to save that info).
+ """
+ def __init__(self, object, method, name=None):
+ if name is None:
+ name = method.__name__
+ self.object = object
+ self.method = method
+ self.name = name
+ setattr(self.object, name, self)
+
+ def __call__(self, *args, **kwargs):
+ nargs = (self.object,) + args
+ return apply(self.method, nargs, kwargs)
+
+ def clone(self, new_object):
+ """
+ Returns an object that re-binds the underlying "method" to
+ the specified new object.
+ """
+ return self.__class__(new_object, self.method, self.name)
+
+class BuilderWrapper(MethodWrapper):
+ """
+ A MethodWrapper subclass that that associates an environment with
+ a Builder.
+
+ This mainly exists to wrap the __call__() function so that all calls
+ to Builders can have their argument lists massaged in the same way
+ (treat a lone argument as the source, treat two arguments as target
+ then source, make sure both target and source are lists) without
+ having to have cut-and-paste code to do it.
+
+ As a bit of obsessive backwards compatibility, we also intercept
+ attempts to get or set the "env" or "builder" attributes, which were
+ the names we used before we put the common functionality into the
+ MethodWrapper base class. We'll keep this around for a while in case
+ people shipped Tool modules that reached into the wrapper (like the
+ Tool/qt.py module does, or did). There shouldn't be a lot attribute
+ fetching or setting on these, so a little extra work shouldn't hurt.
+ """
def __call__(self, target=None, source=_null, *args, **kw):
if source is _null:
source = target
target = [target]
if not source is None and not SCons.Util.is_List(source):
source = [source]
- return apply(self.builder, (self.env, target, source) + args, kw)
+ return apply(MethodWrapper.__call__, (self, target, source) + args, kw)
+
+ def __repr__(self):
+ return '<BuilderWrapper %s>' % repr(self.name)
+
+ def __getattr__(self, name):
+ if name == 'env':
+ return self.object
+ elif name == 'builder':
+ return self.method
+ else:
+ return self.__dict__[name]
+
+ def __setattr__(self, name, value):
+ if name == 'env':
+ self.object = value
+ elif name == 'builder':
+ self.method = value
+ else:
+ self.__dict__[name] = value
# This allows a Builder to be executed directly
# through the Environment to which it's attached.
# But we do have a unit test for this, and can't
# yet rule out that it would be useful in the
# future, so leave it for now.
- def execute(self, **kw):
- kw['env'] = self.env
- apply(self.builder.execute, (), kw)
+ #def execute(self, **kw):
+ # kw['env'] = self.env
+ # apply(self.builder.execute, (), kw)
class BuilderDict(UserDict):
"""This is a dictionary-like class used by an Environment to hold
def __setitem__(self, item, val):
UserDict.__setitem__(self, item, val)
- try:
- self.setenvattr(item, val)
- except AttributeError:
- # Have to catch this because sometimes __setitem__ gets
- # called out of __init__, when we don't have an env
- # attribute yet, nor do we want one!
- pass
-
- def setenvattr(self, item, val):
- """Set the corresponding environment attribute for this Builder.
-
- If the value is already a BuilderWrapper, we pull the builder
- out of it and make another one, so that making a copy of an
- existing BuilderDict is guaranteed separate wrappers for each
- Builder + Environment pair."""
- try:
- builder = val.builder
- except AttributeError:
- builder = val
- setattr(self.env, item, BuilderWrapper(self.env, builder))
+ BuilderWrapper(self.env, val, item)
def __delitem__(self, item):
UserDict.__delitem__(self, item)
self.lookup_list = SCons.Node.arg2nodes_lookups
self._dict = kw.copy()
self._init_special()
+ self.added_methods = []
#self._memo = {}
def _init_special(self):
environment with the specified name. If the name is omitted,
the default name is the name of the function itself.
"""
- SCons.Util.AddMethod(self, function, name)
+ method = MethodWrapper(self, function, name)
+ self.added_methods.append(method)
+
+ def RemoveMethod(self, function):
+ """
+ Removes the specified function's MethodWrapper from the
+ added_methods list, so we don't re-bind it when making a clone.
+ """
+ is_not_func = lambda dm, f=function: not dm.method is f
+ self.added_methods = filter(is_not_func, self.added_methods)
def Override(self, overrides):
"""
# -R dir (deprecated linker rpath)
# IBM compilers may also accept -qframeworkdir=foo
- params = string.split(arg)
+ params = shlex.split(arg)
append_next_arg_to = None # for multi-word args
for arg in params:
if append_next_arg_to:
elif isinstance(s.disambiguate(), SCons.Node.FS.File):
result.append(s)
+def default_decide_source(dependency, target, prev_ni):
+ f = SCons.Defaults.DefaultEnvironment().decide_source
+ return f(dependency, target, prev_ni)
+
+def default_decide_target(dependency, target, prev_ni):
+ f = SCons.Defaults.DefaultEnvironment().decide_target
+ return f(dependency, target, prev_ni)
+
class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction
self.lookup_list = SCons.Node.arg2nodes_lookups
self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
self._init_special()
+ self.added_methods = []
+
+ # We don't use AddMethod, or define these as methods in this
+ # class, because we *don't* want these functions to be bound
+ # methods. They need to operate independently so that the
+ # settings will work properly regardless of whether a given
+ # target ends up being built with a Base environment or an
+ # OverrideEnvironment or what have you.
+ self.decide_target = default_decide_target
+ self.decide_source = default_decide_source
self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
# reserved variable name like TARGETS.
pass
+ SCons.Tool.Initializers(self)
+
if tools is None:
tools = self._dict.get('TOOLS', None)
if tools is None:
except KeyError:
return None
- def get_calculator(self):
- try:
- module = self._calc_module
- c = apply(SCons.Sig.Calculator, (module,), CalculatorArgs)
- except AttributeError:
- # Note that we're calling get_calculator() here, so the
- # DefaultEnvironment() must have a _calc_module attribute
- # to avoid infinite recursion.
- c = SCons.Defaults.DefaultEnvironment().get_calculator()
- return c
-
def get_CacheDir(self):
try:
return self._CacheDir
"""
self._dict.update(dict)
- def use_build_signature(self):
+ def get_src_sig_type(self):
+ try:
+ return self.src_sig_type
+ except AttributeError:
+ t = SCons.Defaults.DefaultEnvironment().src_sig_type
+ self.src_sig_type = t
+ return t
+
+ def get_tgt_sig_type(self):
try:
- return self._build_signature
+ return self.tgt_sig_type
except AttributeError:
- b = SCons.Defaults.DefaultEnvironment()._build_signature
- self._build_signature = b
- return b
+ t = SCons.Defaults.DefaultEnvironment().tgt_sig_type
+ self.tgt_sig_type = t
+ return t
#######################################################################
# Public methods for manipulating an Environment. These begin with
"""
clone = copy.copy(self)
clone._dict = semi_deepcopy(self._dict)
+
try:
cbd = clone._dict['BUILDERS']
- clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
except KeyError:
pass
+ else:
+ clone._dict['BUILDERS'] = BuilderDict(cbd, clone)
- clone._memo = {}
+ clone.added_methods = []
+ for mw in self.added_methods:
+ mw.clone(clone)
- apply_tools(clone, tools, toolpath)
+ clone._memo = {}
- # Apply passed-in variables after the new tools.
+ # Apply passed-in variables before the tools
+ # so the tools can use the new variables
kw = copy_non_reserved_keywords(kw)
new = {}
for key, value in kw.items():
new[key] = SCons.Subst.scons_subst_once(value, self, key)
apply(clone.Replace, (), new)
+
+ apply_tools(clone, tools, toolpath)
+
+ # apply them again in case the tools overwrote them
+ apply(clone.Replace, (), new)
+
if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone')
return clone
def Copy(self, *args, **kw):
return apply(self.Clone, args, kw)
+ def _changed_build(self, dependency, target, prev_ni):
+ if dependency.changed_state(target, prev_ni):
+ return 1
+ return self.decide_source(dependency, target, prev_ni)
+
+ def _changed_content(self, dependency, target, prev_ni):
+ return dependency.changed_content(target, prev_ni)
+
+ def _changed_source(self, dependency, target, prev_ni):
+ target_env = dependency.get_build_env()
+ type = target_env.get_tgt_sig_type()
+ if type == 'source':
+ return target_env.decide_source(dependency, target, prev_ni)
+ else:
+ return target_env.decide_target(dependency, target, prev_ni)
+
+ def _changed_timestamp_then_content(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_then_content(target, prev_ni)
+
+ def _changed_timestamp_newer(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_newer(target, prev_ni)
+
+ def _changed_timestamp_match(self, dependency, target, prev_ni):
+ return dependency.changed_timestamp_match(target, prev_ni)
+
+ def Decider(self, function):
+ if function in ('MD5', 'content'):
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ function = self._changed_content
+ elif function == 'MD5-timestamp':
+ function = self._changed_timestamp_then_content
+ elif function in ('timestamp-newer', 'make'):
+ function = self._changed_timestamp_newer
+ elif function == 'timestamp-match':
+ function = self._changed_timestamp_match
+ elif not callable(function):
+ raise UserError, "Unknown Decider value %s" % repr(function)
+
+ # We don't use AddMethod because we don't want to turn the
+ # function, which only expects three arguments, into a bound
+ # method, which would add self as an initial, fourth argument.
+ self.decide_target = function
+ self.decide_source = function
+
def Detect(self, progs):
"""Return the first available program in progs.
"""
with new construction variables and/or values.
"""
try:
- kwbd = semi_deepcopy(kw['BUILDERS'])
- del kw['BUILDERS']
- self.__setitem__('BUILDERS', kwbd)
+ kwbd = kw['BUILDERS']
except KeyError:
pass
+ else:
+ kwbd = semi_deepcopy(kwbd)
+ del kw['BUILDERS']
+ self.__setitem__('BUILDERS', kwbd)
kw = copy_non_reserved_keywords(kw)
self._update(semi_deepcopy(kw))
self.scanner_map_delete(kw)
def SourceSignatures(self, type):
type = self.subst(type)
+ self.src_sig_type = type
if type == 'MD5':
- try:
- import SCons.Sig.MD5
- except ImportError:
- msg = "No MD5 module available, using time stamps"
- SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
- import SCons.Sig.TimeStamp
- self._calc_module = SCons.Sig.TimeStamp
- else:
- self._calc_module = SCons.Sig.MD5
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ self.decide_source = self._changed_content
elif type == 'timestamp':
- import SCons.Sig.TimeStamp
- self._calc_module = SCons.Sig.TimeStamp
+ self.decide_source = self._changed_timestamp_newer
else:
- raise UserError, "Unknown source signature type '%s'"%type
+ raise UserError, "Unknown source signature type '%s'" % type
def Split(self, arg):
"""This function converts a string or list into a list of strings
def TargetSignatures(self, type):
type = self.subst(type)
- if type == 'build':
- self._build_signature = 1
- elif type == 'content':
- self._build_signature = 0
+ self.tgt_sig_type = type
+ if type in ('MD5', 'content'):
+ if not SCons.Util.md5:
+ raise UserError, "MD5 signatures are not available in this version of Python."
+ self.decide_target = self._changed_content
+ elif type == 'timestamp':
+ self.decide_target = self._changed_timestamp_newer
+ elif type == 'build':
+ self.decide_target = self._changed_build
+ elif type == 'source':
+ self.decide_target = self._changed_source
else:
- raise SCons.Errors.UserError, "Unknown target signature type '%s'"%type
+ raise UserError, "Unknown target signature type '%s'"%type
def Value(self, value, built_value=None):
"""
# remove duplicates
return list(set(sources))
+ def FindInstalledFiles(self):
+ """ returns the list of all targets of the Install and InstallAs Builder.
+ """
+ from SCons.Tool import install
+ if install._UNIQUE_INSTALLED_FILES is None:
+ install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
+ return install._UNIQUE_INSTALLED_FILES
+
class OverrideEnvironment(Base):
"""A proxy that overrides variables in a wrapped construction
environment by returning values from an overrides dictionary in
assert d == empty, d
s = "-I/usr/include/fum -I bar -X\n" + \
+ '-I"C:\\Program Files\\ASCEND\\include" ' + \
"-L/usr/fax -L foo -lxxx -l yyy " + \
+ '-L"C:\\Program Files\\ASCEND" -lascend ' + \
"-Wa,-as -Wl,-link " + \
"-Wl,-rpath=rpath1 " + \
"-Wl,-R,rpath2 " + \
"-pthread " + \
"-mno-cygwin -mwindows " + \
"-arch i386 -isysroot /tmp +DD64 " + \
- "-DFOO -DBAR=value -D BAZ"
+ "-DFOO -DBAR=value -D BAZ "
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',
'+DD64'], d['CCFLAGS']
assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES']
assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS']
- assert d['CPPPATH'] == ['/usr/include/fum', 'bar'], d['CPPPATH']
+ assert d['CPPPATH'] == expect_CPPPATH, d['CPPPATH']
assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH']
assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS']
- assert d['LIBPATH'] == ['/usr/fax', 'foo'], d['LIBPATH']
- assert d['LIBS'] == ['xxx', 'yyy'], d['LIBS']
+ assert d['LIBPATH'] == expect_LIBPATH, d['LIBPATH']
+ LIBS = map(str, d['LIBS'])
+ assert LIBS == expect_LIBS, (d['LIBS'], LIBS)
assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread',
'-mno-cygwin', '-mwindows',
('-arch', 'i386'),
- def test_Builder_execs(self):
+ # This unit test is currently disabled because we don't think the
+ # underlying method it tests (Environment.BuilderWrapper.execute())
+ # is necessary, but we're leaving the code here for now in case
+ # that's mistaken.
+ def _DO_NOT_test_Builder_execs(self):
"""Test Builder execution through different environments
One environment is initialized with a single
assert built_it['out3']
env4 = env3.Clone()
- assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
- assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (env4.builder1.env, env3)
+ assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
+ assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % (
+env4.builder1.env, env3)
# Now test BUILDERS as a dictionary.
built_it = {}
assert built_it['out1']
assert built_it['out2']
+
+
def test_Scanners(self):
"""Test setting SCANNERS in various ways
#
env1 = self.TestEnvironment(BUILDERS = {'b1' : 1})
assert hasattr(env1, 'b1'), "env1.b1 was not set"
- assert env1.b1.env == env1, "b1.env doesn't point to env1"
+ assert env1.b1.object == env1, "b1.object doesn't point to env1"
env2 = env1.Clone(BUILDERS = {'b2' : 2})
assert env2 is env2
assert env2 == env2
assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1"
- assert env1.b1.env == env1, "b1.env was changed"
+ assert env1.b1.object == env1, "b1.object was changed"
assert not hasattr(env2, 'b1'), "b1 was not cleared from env2"
assert hasattr(env2, 'b2'), "env2.b2 was not set"
- assert env2.b2.env == env2, "b2.env doesn't point to env2"
+ assert env2.b2.object == env2, "b2.object doesn't point to env2"
# Ensure that specifying new tools in a copied environment
# works.
env = env.Clone(tools=['yyy'])
assert env['YYY'] == 'two', env['YYY']
+
+ # Test that
+ real_value = [4]
+
+ def my_tool(env, rv=real_value):
+ assert env['KEY_THAT_I_WANT'] == rv[0]
+ env['KEY_THAT_I_WANT'] = rv[0] + 1
+
+ env = self.TestEnvironment()
+
+ real_value[0] = 5
+ env = env.Clone(KEY_THAT_I_WANT=5, tools=[my_tool])
+ assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+ real_value[0] = 6
+ env = env.Clone(KEY_THAT_I_WANT=6, tools=[my_tool])
+ assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT']
+
+
def test_Copy(self):
"""Test copying using the old env.Copy() method"""
env1 = self.TestEnvironment(XXX = 'x', YYY = 'y')
def test_SourceSignatures(type):
"""Test the SourceSignatures() method"""
+ import SCons.Errors
+
env = type.TestEnvironment(M = 'MD5', T = 'timestamp')
exc_caught = None
except SCons.Errors.UserError:
exc_caught = 1
assert exc_caught, "did not catch expected UserError"
- assert not hasattr(env, '_calc_module')
env.SourceSignatures('MD5')
- m = env._calc_module
+ assert env.src_sig_type == 'MD5', env.src_sig_type
env.SourceSignatures('$M')
- assert env._calc_module is m
+ assert env.src_sig_type == 'MD5', env.src_sig_type
env.SourceSignatures('timestamp')
- t = env._calc_module
+ assert env.src_sig_type == 'timestamp', env.src_sig_type
env.SourceSignatures('$T')
- assert env._calc_module is t
+ assert env.src_sig_type == 'timestamp', env.src_sig_type
+
+ try:
+ import SCons.Util
+ save_md5 = SCons.Util.md5
+ SCons.Util.md5 = None
+ try:
+ env.SourceSignatures('MD5')
+ except SCons.Errors.UserError:
+ pass
+ else:
+ self.fail('Did not catch expected UserError')
+ finally:
+ SCons.Util.md5 = save_md5
def test_Split(self):
"""Test the Split() method"""
def test_TargetSignatures(type):
"""Test the TargetSignatures() method"""
+ import SCons.Errors
+
env = type.TestEnvironment(B = 'build', C = 'content')
exc_caught = None
assert not hasattr(env, '_build_signature')
env.TargetSignatures('build')
- assert env._build_signature == 1, env._build_signature
-
- env.TargetSignatures('content')
- assert env._build_signature == 0, env._build_signature
+ assert env.tgt_sig_type == 'build', env.tgt_sig_type
env.TargetSignatures('$B')
- assert env._build_signature == 1, env._build_signature
+ assert env.tgt_sig_type == 'build', env.tgt_sig_type
+
+ env.TargetSignatures('content')
+ assert env.tgt_sig_type == 'content', env.tgt_sig_type
env.TargetSignatures('$C')
- assert env._build_signature == 0, env._build_signature
+ assert env.tgt_sig_type == 'content', env.tgt_sig_type
+
+ env.TargetSignatures('MD5')
+ assert env.tgt_sig_type == 'MD5', env.tgt_sig_type
+
+ env.TargetSignatures('timestamp')
+ assert env.tgt_sig_type == 'timestamp', env.tgt_sig_type
+
+ try:
+ import SCons.Util
+ save_md5 = SCons.Util.md5
+ SCons.Util.md5 = None
+ try:
+ env.TargetSignatures('MD5')
+ except SCons.Errors.UserError:
+ pass
+ else:
+ self.fail('Did not catch expected UserError')
+ try:
+ env.TargetSignatures('content')
+ except SCons.Errors.UserError:
+ pass
+ else:
+ self.fail('Did not catch expected UserError')
+ finally:
+ SCons.Util.md5 = save_md5
def test_Value(self):
"""Test creating a Value() object
def get_action_list(self):
return self.pre_actions + self.action_list + self.post_actions
+ memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
+
def get_build_env(self):
"""Fetch or create the appropriate build Environment
for this Executor.
"""
+ try:
+ return self._memo['get_build_env']
+ except KeyError:
+ pass
+
# Create the build environment instance with appropriate
# overrides. These get evaluated against the current
# environment's construction variables so that users can
env = self.env or SCons.Defaults.DefaultEnvironment()
build_env = env.Override(overrides)
+ self._memo['get_build_env'] = build_env
+
return build_env
def get_build_scanner_path(self, scanner):
- """Fetch the scanner path for this executor's targets
- and sources.
+ """Fetch the scanner path for this executor's targets and sources.
"""
env = self.get_build_env()
try:
result.update(kw)
return result
- def do_nothing(self, target, exitstatfunc, kw):
- pass
+ def do_nothing(self, target, kw):
+ return 0
- def do_execute(self, target, exitstatfunc, kw):
+ def do_execute(self, target, kw):
"""Actually execute the action list."""
env = self.get_build_env()
kw = self.get_kw(kw)
+ status = 0
for act in self.get_action_list():
- apply(act,
- (self.targets, self.sources, env, exitstatfunc),
- kw)
+ status = apply(act, (self.targets, self.sources, env), kw)
+ if status:
+ break
+ return status
# use extra indirection because with new-style objects (Python 2.2
# and above) we can't override special methods, and nullify() needs
# to be able to do this.
- def __call__(self, target, exitstatfunc, **kw):
- self.do_execute(target, exitstatfunc, kw)
+ def __call__(self, target, **kw):
+ return self.do_execute(target, kw)
def cleanup(self):
self._memo = {}
scanner_list = map(select_specific_scanner, scanner_list)
scanner_list = filter(remove_null_scanners, scanner_list)
scanner_path_list = map(add_scanner_path, scanner_list)
+
deps = []
for node, scanner, path in scanner_path_list:
deps.extend(node.get_implicit_deps(env, scanner, path))
+ deps.extend(self.get_implicit_deps())
+
for tgt in self.targets:
tgt.add_to_implicit(deps)
return result
+ def get_implicit_deps(self):
+ """Return the executor's implicit dependencies, i.e. the nodes of
+ the commands to be executed."""
+ result = []
+ build_env = self.get_build_env()
+ for act in self.get_action_list():
+ result.extend(act.get_implicit_deps(self.targets, self.sources, build_env))
+ return result
+
_Executor = Executor
apply(_Executor.__init__, (self,), kw)
def get_build_env(self):
import SCons.Util
- return SCons.Util.Null()
+ 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)
+ def get_CacheDir(self):
+ import SCons.CacheDir
+ return SCons.CacheDir.Null()
+ return NullEnvironment()
def get_build_scanner_path(self):
return None
def cleanup(self):
class MyAction:
def __init__(self, actions=['action1', 'action2']):
self.actions = actions
- def __call__(self, target, source, env, errfunc, **kw):
+ def __call__(self, target, source, env, **kw):
for action in self.actions:
- apply(action, (target, source, env, errfunc), kw)
+ apply(action, (target, source, env), kw)
def genstring(self, target, source, env):
return string.join(['GENSTRING'] + map(str, self.actions) + target + source)
def get_contents(self, target, source, env):
return string.join(self.actions + target + source)
+ def get_implicit_deps(self, target, source, env):
+ return []
class MyBuilder:
def __init__(self, env, overrides):
self.missing_val = None
def __str__(self):
return self.name
- def build(self, errfunc=None):
+ def build(self):
executor = SCons.Executor.Executor(MyAction(self.pre_actions +
[self.builder.action] +
self.post_actions),
[],
[self],
['s1', 's2'])
- apply(executor, (self, errfunc), {})
+ apply(executor, (self), {})
def get_env_scanner(self, env, kw):
return MyScanner('dep-')
def get_implicit_deps(self, env, scanner, path):
def test__call__(self):
"""Test calling an Executor"""
result = []
- def pre(target, source, env, errfunc, result=result, **kw):
+ def pre(target, source, env, result=result, **kw):
result.append('pre')
- def action1(target, source, env, errfunc, result=result, **kw):
+ def action1(target, source, env, result=result, **kw):
result.append('action1')
- def action2(target, source, env, errfunc, result=result, **kw):
+ def action2(target, source, env, result=result, **kw):
result.append('action2')
- def post(target, source, env, errfunc, result=result, **kw):
+ def post(target, source, env, result=result, **kw):
result.append('post')
env = MyEnvironment()
x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
x.add_pre_action(pre)
x.add_post_action(post)
- x(t, lambda x: x)
+ x(t)
assert result == ['pre', 'action1', 'action2', 'post'], result
del result[:]
- def pre_err(target, source, env, errfunc, result=result, **kw):
+ def pre_err(target, source, env, result=result, **kw):
result.append('pre_err')
- if errfunc:
- errfunc(1)
return 1
x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
x.add_pre_action(pre_err)
x.add_post_action(post)
- x(t, lambda x: x)
- assert result == ['pre_err', 'action1', 'action2', 'post'], result
- del result[:]
-
- def errfunc(stat):
- raise "errfunc %s" % stat
-
- try:
- x(t, errfunc)
- except:
- assert sys.exc_type == "errfunc 1", sys.exc_type
- else:
- assert 0, "did not catch expected exception"
+ x(t)
assert result == ['pre_err'], result
del result[:]
env = MyEnvironment(S='string')
result = []
- def action1(target, source, env, errfunc, result=result, **kw):
+ def action1(target, source, env, result=result, **kw):
result.append('action1')
env = MyEnvironment()
a = MyAction([action1])
x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2'])
- x(MyNode('', [], []), None)
+ x(MyNode('', [], []))
assert result == ['action1'], result
s = str(x)
assert s[:10] == 'GENSTRING ', s
x.nullify()
assert result == [], result
- x(MyNode('', [], []), None)
+ x(MyNode('', [], []))
assert result == [], result
s = str(x)
assert s == '', s
t1 = MyNode('t1')
t2 = MyNode('t2')
sources = [MyNode('s1'), MyNode('s2')]
- x = SCons.Executor.Executor('b', env, [{}], [t1, t2], sources)
+ x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)
deps = x.scan_targets(None)
assert t1.implicit == ['dep-t1', 'dep-t2'], t1.implicit
t1 = MyNode('t1')
t2 = MyNode('t2')
sources = [MyNode('s1'), MyNode('s2')]
- x = SCons.Executor.Executor('b', env, [{}], [t1, t2], sources)
+ x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources)
deps = x.scan_sources(None)
assert t1.implicit == ['dep-s1', 'dep-s2'], t1.implicit
import SCons.Node
import time
+class DummyNodeInfo:
+ def update(self, obj):
+ pass
class testnode (SCons.Node.Node):
def __init__(self):
SCons.Node.Node.__init__(self)
self.expect_to_be = SCons.Node.executed
+ self.ninfo = DummyNodeInfo()
class goodnode (testnode):
- pass
+ def __init__(self):
+ SCons.Node.Node.__init__(self)
+ self.expect_to_be = SCons.Node.up_to_date
+ self.ninfo = DummyNodeInfo()
class slowgoodnode (goodnode):
def prepare(self):
# mislabelling of results).
for N in testnodes:
- self.failUnless(N.get_state() in [SCons.Node.no_state, N.expect_to_be],
- "node ran but got unexpected result")
+ state = N.get_state()
+ self.failUnless(state in [SCons.Node.no_state, N.expect_to_be],
+ "Node %s got unexpected result: %s" % (N, state))
self.failUnless(filter(lambda N: N.get_state(), testnodes),
"no nodes ran at all.")
return None
class AliasNodeInfo(SCons.Node.NodeInfoBase):
- pass
+ current_version_id = 1
+ field_list = ['csig']
+ def str_to_node(self, s):
+ return default_ans.Alias(s)
class AliasBuildInfo(SCons.Node.BuildInfoBase):
- pass
+ current_version_id = 1
class Alias(SCons.Node.Node):
def __str__(self):
return self.name
+ def make_ready(self):
+ self.get_csig()
+
really_build = SCons.Node.Node.build
- current = SCons.Node.Node.children_are_up_to_date
+ is_up_to_date = SCons.Node.Node.children_are_up_to_date
def is_under(self, dir):
# Make Alias nodes get built regardless of
#
#
+ def changed_since_last_build(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
def build(self):
"""A "builder" for aliases."""
pass
self.reset_executor()
self.build = self.really_build
+ def get_csig(self):
+ """
+ Generate a node's content signature, the digested signature
+ of its content.
+
+ node - the node
+ cache - alternate node to use for the signature cache
+ returns - the content signature
+ """
+ try:
+ return self.ninfo.csig
+ except AttributeError:
+ pass
+
+ contents = self.get_contents()
+ csig = SCons.Util.MD5signature(contents)
+ self.get_ninfo().csig = csig
+ return csig
+
default_ans = AliasNameSpace()
SCons.Node.arg2nodes_lookups.append(default_ans.lookup)
import SCons.Errors
import SCons.Memoize
import SCons.Node
+import SCons.Node.Alias
import SCons.Subst
import SCons.Util
import SCons.Warnings
+from SCons.Debug import Trace
+
# The max_drift value: by default, use a cached signature value for
# any file that's been untouched for more than two days.
default_max_drift = 2*24*60*60
assert directory, "A directory must be provided"
self.abspath = directory.entry_abspath(name)
+ self.labspath = directory.entry_labspath(name)
if directory.path == '.':
self.path = name
else:
directory."""
return self.disambiguate().exists()
- def rel_path(self, other):
- d = self.disambiguate()
- if d.__class__ == Entry:
- raise "rel_path() could not disambiguate File/Dir"
- return d.rel_path(other)
+# def rel_path(self, other):
+# d = self.disambiguate()
+# if d.__class__ == Entry:
+# raise "rel_path() could not disambiguate File/Dir"
+# return d.rel_path(other)
+
+ def new_ninfo(self):
+ return self.disambiguate().new_ninfo()
+
+ def changed_since_last_build(self, target, prev_ni):
+ return self.disambiguate().changed_since_last_build(target, prev_ni)
# This is for later so we can differentiate between Entry the class and Entry
# the method of the FS class.
self.pathTop = path
self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
- self.Top = self._doLookup(Dir, os.path.normpath(self.pathTop))
+ self.Top = self.Dir(self.pathTop)
self.Top.path = '.'
self.Top.tpath = '.'
self._cwd = self.Top
+ DirNodeInfo.top = self.Top
+ FileNodeInfo.top = self.Top
+
def set_SConstruct_dir(self, dir):
self.SConstruct_dir = dir
def getcwd(self):
return self._cwd
- def _doLookup_key(self, fsclass, name, directory = None, create = 1):
- return (fsclass, name, directory)
-
- memoizer_counters.append(SCons.Memoize.CountDict('_doLookup', _doLookup_key))
-
- def _doLookup(self, fsclass, name, directory = None, create = 1):
- """This method differs from the File and Dir factory methods in
- one important way: the meaning of the directory parameter.
- In this method, if directory is None or not supplied, the supplied
- name is expected to be an absolute path. If you try to look up a
- relative path with directory=None, then an AssertionError will be
- raised.
- """
- memo_key = (fsclass, name, directory)
- try:
- memo_dict = self._memo['_doLookup']
- except KeyError:
- memo_dict = {}
- self._memo['_doLookup'] = memo_dict
- else:
- try:
- return memo_dict[memo_key]
- except KeyError:
- pass
-
- if not name:
- # This is a stupid hack to compensate for the fact that the
- # POSIX and Windows versions of os.path.normpath() behave
- # differently in older versions of Python. In particular,
- # in POSIX:
- # os.path.normpath('./') == '.'
- # in Windows:
- # os.path.normpath('./') == ''
- # os.path.normpath('.\\') == ''
- #
- # This is a definite bug in the Python library, but we have
- # to live with it.
- name = '.'
- path_orig = string.split(name, os.sep)
- path_norm = string.split(_my_normcase(name), os.sep)
-
- first_orig = path_orig.pop(0) # strip first element
- unused = path_norm.pop(0) # strip first element
-
- drive, path_first = os.path.splitdrive(first_orig)
- if path_first:
- path_orig = [ path_first, ] + path_orig
- path_norm = [ _my_normcase(path_first), ] + path_norm
- else:
- # Absolute path
- drive = _my_normcase(drive)
- try:
- directory = self.Root[drive]
- except KeyError:
- if not create:
- raise SCons.Errors.UserError
- directory = RootDir(drive, self)
- self.Root[drive] = directory
- if not drive:
- self.Root[self.defaultDrive] = directory
- elif drive == self.defaultDrive:
- self.Root[''] = directory
-
- if not path_orig:
- memo_dict[memo_key] = directory
- return directory
-
- last_orig = path_orig.pop() # strip last element
- last_norm = path_norm.pop() # strip last element
-
- # Lookup the directory
- for orig, norm in map(None, path_orig, path_norm):
- try:
- entries = directory.entries
- except AttributeError:
- # We tried to look up the entry in either an Entry or
- # a File. Give whatever it is a chance to do what's
- # appropriate: morph into a Dir or raise an exception.
- directory.must_be_same(Dir)
- entries = directory.entries
- try:
- directory = entries[norm]
- except KeyError:
- if not create:
- raise SCons.Errors.UserError
-
- d = Dir(orig, directory, self)
-
- # Check the file system (or not, as configured) to make
- # sure there isn't already a file there.
- d.diskcheck_match()
-
- directory.entries[norm] = d
- directory.add_wkid(d)
- directory = d
-
- directory.must_be_same(Dir)
-
- try:
- e = directory.entries[last_norm]
- except KeyError:
- if not create:
- raise SCons.Errors.UserError
-
- result = fsclass(last_orig, directory, self)
-
- # Check the file system (or not, as configured) to make
- # sure there isn't already a directory at the path on
- # disk where we just created a File node, and vice versa.
- result.diskcheck_match()
-
- directory.entries[last_norm] = result
- directory.add_wkid(result)
- else:
- e.must_be_same(fsclass)
- result = e
-
- memo_dict[memo_key] = result
-
- return result
-
- def _transformPath(self, name, directory):
- """Take care of setting up the correct top-level directory,
- usually in preparation for a call to doLookup().
-
- If the path name is prepended with a '#', then it is unconditionally
- interpreted as relative to the top-level directory of this FS.
-
- If directory is None, and name is a relative path,
- then the same applies.
- """
- try:
- # Decide if this is a top-relative look up. The normal case
- # (by far) is handed a non-zero-length string to look up,
- # so just (try to) check for the initial '#'.
- top_relative = (name[0] == '#')
- except (AttributeError, IndexError):
- # The exceptions we may encounter in unusual cases:
- # AttributeError: a proxy without a __getitem__() method.
- # IndexError: a null string.
- top_relative = False
- name = str(name)
- if top_relative:
- directory = self.Top
- name = name[1:]
- if name and (name[0] == os.sep or name[0] == '/'):
- # Correct such that '#/foo' is equivalent
- # to '#foo'.
- name = name[1:]
- name = os.path.normpath(os.path.join('.', name))
- return (name, directory)
- elif not directory:
- directory = self._cwd
- return (os.path.normpath(name), directory)
-
def chdir(self, dir, change_os_dir=0):
"""Change the current working directory for lookups.
If change_os_dir is true, we will also change the "real" cwd
self._cwd = curr
raise
- def Entry(self, name, directory = None, create = 1, klass=None):
+ def get_root(self, drive):
+ """
+ Returns the root directory for the specified drive, creating
+ it if necessary.
+ """
+ drive = _my_normcase(drive)
+ try:
+ return self.Root[drive]
+ except KeyError:
+ root = RootDir(drive, self)
+ self.Root[drive] = root
+ if not drive:
+ self.Root[self.defaultDrive] = root
+ elif drive == self.defaultDrive:
+ self.Root[''] = root
+ return root
+
+ def _lookup(self, p, directory, fsclass, create=1):
+ """
+ The generic entry point for Node lookup with user-supplied data.
+
+ This translates arbitrary input into a canonical Node.FS object
+ of the specified fsclass. The general approach for strings is
+ to turn it into a normalized absolute path and then call the
+ root directory's lookup_abs() method for the heavy lifting.
+
+ If the path name begins with '#', it is unconditionally
+ interpreted relative to the top-level directory of this FS.
+
+ If the path name is relative, then the path is looked up relative
+ to the specified directory, or the current directory (self._cwd,
+ typically the SConscript directory) if the specified directory
+ is None.
+ """
+ if isinstance(p, Base):
+ # It's already a Node.FS object. Make sure it's the right
+ # class and return.
+ p.must_be_same(fsclass)
+ return p
+ # str(p) in case it's something like a proxy object
+ p = str(p)
+ drive, p = os.path.splitdrive(p)
+ if drive and not p:
+ # A drive letter without a path...
+ p = os.sep
+ root = self.get_root(drive)
+ elif os.path.isabs(p):
+ # An absolute path...
+ p = os.path.normpath(p)
+ root = self.get_root(drive)
+ else:
+ if p[0:1] == '#':
+ # A top-relative path...
+ directory = self.Top
+ offset = 1
+ if p[1:2] in(os.sep, '/'):
+ offset = 2
+ p = p[offset:]
+ else:
+ # A relative path...
+ if not directory:
+ # ...to the current (SConscript) directory.
+ directory = self._cwd
+ elif not isinstance(directory, Dir):
+ # ...to the specified directory.
+ directory = self.Dir(directory)
+ p = os.path.normpath(directory.labspath + '/' + p)
+ root = directory.root
+ if os.sep != '/':
+ p = string.replace(p, os.sep, '/')
+ return root._lookup_abs(p, fsclass, create)
+
+ def Entry(self, name, directory = None, create = 1):
"""Lookup or create a generic Entry node with the specified name.
If the name is a relative path (begins with ./, ../, or a file
name), then it is looked up relative to the supplied directory
node, or to the top level directory of the FS (supplied at
construction time) if no directory is supplied.
"""
-
- if not klass:
- klass = Entry
-
- if isinstance(name, Base):
- name.must_be_same(klass)
- return name
- else:
- if directory and not isinstance(directory, Dir):
- directory = self.Dir(directory)
- name, directory = self._transformPath(name, directory)
- return self._doLookup(klass, name, directory, create)
+ return self._lookup(name, directory, Entry, create)
def File(self, name, directory = None, create = 1):
"""Lookup or create a File node with the specified name. If
This method will raise TypeError if a directory is found at the
specified path.
"""
- return self.Entry(name, directory, create, File)
+ return self._lookup(name, directory, File, create)
def Dir(self, name, directory = None, create = 1):
"""Lookup or create a Dir node with the specified name. If
This method will raise TypeError if a normal file is found at the
specified path.
"""
- return self.Entry(name, directory, create, Dir)
+ return self._lookup(name, directory, Dir, create)
def BuildDir(self, build_dir, src_dir, duplicate=1):
"""Link the supplied build directory to the source directory
return targets, message
class DirNodeInfo(SCons.Node.NodeInfoBase):
- pass
+ # This should get reset by the FS initialization.
+ current_version_id = 1
+
+ top = None
+
+ def str_to_node(self, s):
+ top = self.top
+ if os.path.isabs(s):
+ n = top.fs._lookup(s, top, Entry)
+ else:
+ s = top.labspath + '/' + s
+ n = top.root._lookup_abs(s, Entry)
+ return n
class DirBuildInfo(SCons.Node.BuildInfoBase):
- pass
+ current_version_id = 1
class Dir(Base):
"""A class for directories in a file system.
self.searched = 0
self._sconsign = None
self.build_dirs = []
+ self.root = self.dir.root
# Don't just reset the executor, replace its action list,
# because it might have some pre-or post-actions that need to
node.duplicate = node.get_dir().duplicate
def Entry(self, name):
- """Create an entry node named 'name' relative to this directory."""
+ """
+ Looks up or creates an entry node named 'name' relative to
+ this directory.
+ """
return self.fs.Entry(name, self)
def Dir(self, name):
- """Create a directory node named 'name' relative to this directory."""
+ """
+ Looks up or creates a directory node named 'name' relative to
+ this directory.
+ """
dir = self.fs.Dir(name, self)
return dir
def File(self, name):
- """Create a file node named 'name' relative to this directory."""
+ """
+ Looks up or creates a file node named 'name' relative to
+ this directory.
+ """
return self.fs.File(name, self)
+ def _lookup_rel(self, name, klass, create=1):
+ """
+ Looks up a *normalized* relative path name, relative to this
+ directory.
+
+ This method is intended for use by internal lookups with
+ already-normalized path data. For general-purpose lookups,
+ use the Entry(), Dir() and File() methods above.
+
+ This method does *no* input checking and will die or give
+ incorrect results if it's passed a non-normalized path name (e.g.,
+ a path containing '..'), an absolute path name, a top-relative
+ ('#foo') path name, or any kind of object.
+ """
+ name = self.labspath + '/' + name
+ return self.root._lookup_abs(name, klass, create)
+
def link(self, srcdir, duplicate):
"""Set this directory as the build directory for the
supplied source directory."""
def up(self):
return self.entries['..']
- def _rel_path_key(self, other):
- return str(other)
-
- memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
-
- def rel_path(self, other):
- """Return a path to "other" relative to this directory.
- """
- try:
- memo_dict = self._memo['rel_path']
- except KeyError:
- memo_dict = {}
- self._memo['rel_path'] = memo_dict
- else:
- try:
- return memo_dict[other]
- except KeyError:
- pass
-
- if self is other:
-
- result = '.'
-
- elif not other in self.path_elements:
-
- try:
- other_dir = other.get_dir()
- except AttributeError:
- result = str(other)
- else:
- if other_dir is None:
- result = other.name
- else:
- dir_rel_path = self.rel_path(other_dir)
- if dir_rel_path == '.':
- result = other.name
- else:
- result = dir_rel_path + os.sep + other.name
-
- else:
-
- i = self.path_elements.index(other) + 1
-
- path_elems = ['..'] * (len(self.path_elements) - i) \
- + map(lambda n: n.name, other.path_elements[i:])
-
- result = string.join(path_elems, os.sep)
-
- memo_dict[other] = result
-
- return result
+# This complicated method, which constructs relative paths between
+# arbitrary Node.FS objects, is no longer used. It was introduced to
+# store dependency paths in .sconsign files relative to the target, but
+# that ended up being significantly inefficient. We're leaving the code
+# here, commented out, because it would be too easy for someone to decide
+# to re-invent this wheel in the future (if it becomes necessary) because
+# they didn't know this code was buried in some source-code change from
+# the distant past...
+#
+# def _rel_path_key(self, other):
+# return str(other)
+#
+# memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
+#
+# def rel_path(self, other):
+# """Return a path to "other" relative to this directory.
+# """
+# try:
+# memo_dict = self._memo['rel_path']
+# except KeyError:
+# memo_dict = {}
+# self._memo['rel_path'] = memo_dict
+# else:
+# try:
+# return memo_dict[other]
+# except KeyError:
+# pass
+#
+# if self is other:
+#
+# result = '.'
+#
+# elif not other in self.path_elements:
+#
+# try:
+# other_dir = other.get_dir()
+# except AttributeError:
+# result = str(other)
+# else:
+# if other_dir is None:
+# result = other.name
+# else:
+# dir_rel_path = self.rel_path(other_dir)
+# if dir_rel_path == '.':
+# result = other.name
+# else:
+# result = dir_rel_path + os.sep + other.name
+#
+# else:
+#
+# i = self.path_elements.index(other) + 1
+#
+# path_elems = ['..'] * (len(self.path_elements) - i) \
+# + map(lambda n: n.name, other.path_elements[i:])
+#
+# result = string.join(path_elems, os.sep)
+#
+# memo_dict[other] = result
+#
+# return result
def get_env_scanner(self, env, kw={}):
import SCons.Defaults
self.clear()
return scanner(self, env, path)
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def prepare(self):
+ pass
+
def build(self, **kw):
"""A null "builder" for directories."""
global MkdirBuilder
if not self.builder is MkdirBuilder:
apply(SCons.Node.Node.build, [self,], kw)
+ #
+ #
+ #
+
def _create(self):
"""Create this directory, silently and without worrying about
whether the builder is the default or not."""
contents = map(lambda n: n.get_contents(), self.children())
return string.join(contents, '')
- def prepare(self):
- pass
-
def do_duplicate(self, src):
pass
- def current(self, calc=None):
+ changed_since_last_build = SCons.Node.Node.state_has_changed
+
+ def is_up_to_date(self):
"""If any child is not up-to-date, then this directory isn't,
either."""
if not self.builder is MkdirBuilder and not self.exists():
def entry_abspath(self, name):
return self.abspath + os.sep + name
+ def entry_labspath(self, name):
+ return self.labspath + '/' + name
+
def entry_path(self, name):
return self.path + os.sep + name
def func(node):
if (isinstance(node, File) or isinstance(node, Entry)) and \
- (node.is_derived() or node.is_pseudo_derived() or node.exists()):
+ (node.is_derived() or node.exists()):
return node
return None
# attribute) so we have to set up some values so Base.__init__()
# won't gag won't it calls some of our methods.
self.abspath = ''
+ self.labspath = ''
self.path = ''
self.tpath = ''
self.path_elements = []
self.duplicate = 0
+ self.root = self
Base.__init__(self, name, self, fs)
# Now set our paths to what we really want them to be: the
- # initial drive letter (the name) plus the directory separator.
+ # initial drive letter (the name) plus the directory separator,
+ # except for the "lookup abspath," which does not have the
+ # drive letter.
self.abspath = name + os.sep
+ self.labspath = '/'
self.path = name + os.sep
self.tpath = name + os.sep
self._morph()
+ self._lookupDict = {}
+
+ # The // and os.sep + os.sep entries are necessary because
+ # os.path.normpath() seems to preserve double slashes at the
+ # beginning of a path (presumably for UNC path names), but
+ # collapses triple slashes to a single slash.
+ self._lookupDict['/'] = self
+ self._lookupDict['//'] = self
+ self._lookupDict[os.sep] = self
+ self._lookupDict[os.sep + os.sep] = self
+
def must_be_same(self, klass):
if klass is Dir:
return
Base.must_be_same(self, klass)
+ def _lookup_abs(self, p, klass, create=1):
+ """
+ Fast (?) lookup of a *normalized* absolute path.
+
+ This method is intended for use by internal lookups with
+ already-normalized path data. For general-purpose lookups,
+ use the FS.Entry(), FS.Dir() or FS.File() methods.
+
+ The caller is responsible for making sure we're passed a
+ normalized absolute path; we merely let Python's dictionary look
+ up and return the One True Node.FS object for the path.
+
+ If no Node for the specified "p" doesn't already exist, and
+ "create" is specified, the Node may be created after recursive
+ invocation to find or create the parent directory or directories.
+ """
+ k = _my_normcase(p)
+ try:
+ result = self._lookupDict[k]
+ except KeyError:
+ if not create:
+ raise SCons.Errors.UserError
+ # There is no Node for this path name, and we're allowed
+ # to create it.
+ dir_name, file_name = os.path.split(p)
+ dir_node = self._lookup_abs(dir_name, Dir)
+ result = klass(file_name, dir_node, self.fs)
+ self._lookupDict[k] = result
+ dir_node.entries[_my_normcase(file_name)] = result
+ dir_node.implicit = None
+
+ # Double-check on disk (as configured) that the Node we
+ # created matches whatever is out there in the real world.
+ result.diskcheck_match()
+ else:
+ # There is already a Node for this path name. Allow it to
+ # complain if we were looking for an inappropriate type.
+ result.must_be_same(klass)
+ return result
+
def __str__(self):
return self.abspath
def entry_abspath(self, name):
return self.abspath + name
+ def entry_labspath(self, name):
+ return self.labspath + name
+
def entry_path(self, name):
return self.path + name
return _null
class FileNodeInfo(SCons.Node.NodeInfoBase):
- def __init__(self, node):
- SCons.Node.NodeInfoBase.__init__(self, node)
- self.update(node)
- def __cmp__(self, other):
- try: return cmp(self.bsig, other.bsig)
- except AttributeError: return 1
- def update(self, node):
- self.timestamp = node.get_timestamp()
- self.size = node.getsize()
+ current_version_id = 1
+
+ field_list = ['csig', 'timestamp', 'size']
+
+ # This should get reset by the FS initialization.
+ top = None
+
+ def str_to_node(self, s):
+ top = self.top
+ if os.path.isabs(s):
+ n = top.fs._lookup(s, top, Entry)
+ else:
+ s = top.labspath + '/' + s
+ n = top.root._lookup_abs(s, Entry)
+ return n
class FileBuildInfo(SCons.Node.BuildInfoBase):
- def __init__(self, node):
- SCons.Node.BuildInfoBase.__init__(self, node)
- self.node = node
+ current_version_id = 1
+
def convert_to_sconsign(self):
- """Convert this FileBuildInfo object for writing to a .sconsign file
+ """
+ Converts this FileBuildInfo object for writing to a .sconsign file
- We hung onto the node that we refer to so that we can translate
- the lists of bsources, bdepends and bimplicit Nodes into strings
- relative to the node, but we don't want to write out that Node
- itself to the .sconsign file, so we delete the attribute in
- preparation.
+ This replaces each Node in our various dependency lists with its
+ usual string representation: relative to the top-level SConstruct
+ directory, or an absolute path if it's outside.
"""
- rel_path = self.node.rel_path
- delattr(self, 'node')
+ if os.sep == '/':
+ node_to_str = str
+ else:
+ def node_to_str(n):
+ try:
+ s = n.path
+ except AttributeError:
+ s = str(n)
+ else:
+ s = string.replace(s, os.sep, '/')
+ return s
for attr in ['bsources', 'bdepends', 'bimplicit']:
try:
val = getattr(self, attr)
except AttributeError:
pass
else:
- setattr(self, attr, map(rel_path, val))
+ setattr(self, attr, map(node_to_str, val))
def convert_from_sconsign(self, dir, name):
- """Convert a newly-read FileBuildInfo object for in-SCons use
-
- An on-disk BuildInfo comes without a reference to the node for
- which it's intended, so we have to convert the arguments and add
- back a self.node attribute. We don't worry here about converting
- the bsources, bdepends and bimplicit lists from strings to Nodes
- because they're not used in the normal case of just deciding
- whether or not to rebuild things.
"""
- self.node = dir.Entry(name)
+ Converts a newly-read FileBuildInfo object for in-SCons use
+
+ For normal up-to-date checking, we don't have any conversion to
+ perform--but we're leaving this method here to make that clear.
+ """
+ pass
def prepare_dependencies(self):
- """Prepare a FileBuildInfo object for explaining what changed
+ """
+ Prepares a FileBuildInfo object for explaining what changed
- The bsources, bdepends and bimplicit lists have all been stored
- on disk as paths relative to the Node for which they're stored
- as dependency info. Convert the strings to actual Nodes (for
- use by the --debug=explain code and --implicit-cache).
+ The bsources, bdepends and bimplicit lists have all been
+ stored on disk as paths relative to the top-level SConstruct
+ directory. Convert the strings to actual Nodes (for use by the
+ --debug=explain code and --implicit-cache).
"""
- def str_to_node(s, entry=self.node.dir.Entry):
- # This is a little bogus; we're going to mimic the lookup
- # order of env.arg2nodes() by hard-coding an Alias lookup
- # before we assume it's an Entry. This should be able to
- # go away once the Big Signature Refactoring pickles the
- # actual NodeInfo object, which will let us know precisely
- # what type of Node to turn it into.
- import SCons.Node.Alias
- n = SCons.Node.Alias.default_ans.lookup(s)
- if not n:
- n = entry(s)
- return n
- for attr in ['bsources', 'bdepends', 'bimplicit']:
+ attrs = [
+ ('bsources', 'bsourcesigs'),
+ ('bdepends', 'bdependsigs'),
+ ('bimplicit', 'bimplicitsigs'),
+ ]
+ for (nattr, sattr) in attrs:
try:
- val = getattr(self, attr)
+ strings = getattr(self, nattr)
+ nodeinfos = getattr(self, sattr)
except AttributeError:
pass
else:
- setattr(self, attr, map(str_to_node, val))
- def format(self):
- result = [ self.ninfo.format() ]
+ nodes = []
+ for s, ni in zip(strings, nodeinfos):
+ if not isinstance(s, SCons.Node.Node):
+ s = ni.str_to_node(s)
+ nodes.append(s)
+ setattr(self, nattr, nodes)
+ def format(self, names=0):
+ result = []
bkids = self.bsources + self.bdepends + self.bimplicit
bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
- for i in xrange(len(bkids)):
- result.append(str(bkids[i]) + ': ' + bkidsigs[i].format())
+ for bkid, bkidsig in zip(bkids, bkidsigs):
+ result.append(str(bkid) + ': ' +
+ string.join(bkidsig.format(names=names), ' '))
+ result.append('%s [%s]' % (self.bactsig, self.bact))
return string.join(result, '\n')
-class NodeInfo(FileNodeInfo):
- pass
-
-class BuildInfo(FileBuildInfo):
- pass
-
class File(Base):
"""A class for files in a file system.
"""
if not hasattr(self, '_local'):
self._local = 0
+ # If there was already a Builder set on this entry, then
+ # we need to make sure we call the target-decider function,
+ # not the source-decider. Reaching in and doing this by hand
+ # is a little bogus. We'd prefer to handle this by adding
+ # an Entry.builder_set() method that disambiguates like the
+ # other methods, but that starts running into problems with the
+ # fragile way we initialize Dir Nodes with their Mkdir builders,
+ # yet still allow them to be overridden by the user. Since it's
+ # not clear right now how to fix that, stick with what works
+ # until it becomes clear...
+ if self.has_builder():
+ self.changed_since_last_build = self.decide_target
+
def scanner_key(self):
return self.get_suffix()
raise
return r
+ memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
+
+ def get_size(self):
+ try:
+ return self._memo['get_size']
+ except KeyError:
+ pass
+
+ if self.rexists():
+ size = self.rfile().getsize()
+ else:
+ size = 0
+
+ self._memo['get_size'] = size
+
+ return size
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
+
def get_timestamp(self):
+ try:
+ return self._memo['get_timestamp']
+ except KeyError:
+ pass
+
if self.rexists():
- return self.rfile().getmtime()
+ timestamp = self.rfile().getmtime()
else:
- return 0
+ timestamp = 0
+
+ self._memo['get_timestamp'] = timestamp
+
+ return timestamp
- def store_info(self, obj):
+ def store_info(self):
# Merge our build information into the already-stored entry.
# This accomodates "chained builds" where a file that's a target
# in one build (SConstruct file) is a source in a different build.
# See test/chained-build.py for the use case.
- entry = self.get_stored_info()
- entry.merge(obj)
- self.dir.sconsign().set_entry(self.name, entry)
+ self.dir.sconsign().store_info(self.name, self)
+
+ convert_copy_attrs = [
+ 'bsources',
+ 'bimplicit',
+ 'bdepends',
+ 'bact',
+ 'bactsig',
+ 'ninfo',
+ ]
+
+
+ convert_sig_attrs = [
+ 'bsourcesigs',
+ 'bimplicitsigs',
+ 'bdependsigs',
+ ]
+
+ def convert_old_entry(self, old_entry):
+ # Convert a .sconsign entry from before the Big Signature
+ # Refactoring, doing what we can to convert its information
+ # to the new .sconsign entry format.
+ #
+ # The old format looked essentially like this:
+ #
+ # BuildInfo
+ # .ninfo (NodeInfo)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bsources
+ # .bsourcesigs ("signature" list)
+ # .bdepends
+ # .bdependsigs ("signature" list)
+ # .bimplicit
+ # .bimplicitsigs ("signature" list)
+ # .bact
+ # .bactsig
+ #
+ # The new format looks like this:
+ #
+ # .ninfo (NodeInfo)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .binfo (BuildInfo)
+ # .bsources
+ # .bsourcesigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bdepends
+ # .bdependsigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bimplicit
+ # .bimplicitsigs (NodeInfo list)
+ # .bsig
+ # .csig
+ # .timestamp
+ # .size
+ # .bact
+ # .bactsig
+ #
+ # The basic idea of the new structure is that a NodeInfo always
+ # holds all available information about the state of a given Node
+ # at a certain point in time. The various .b*sigs lists can just
+ # be a list of pointers to the .ninfo attributes of the different
+ # dependent nodes, without any copying of information until it's
+ # time to pickle it for writing out to a .sconsign file.
+ #
+ # The complicating issue is that the *old* format only stored one
+ # "signature" per dependency, based on however the *last* build
+ # was configured. We don't know from just looking at it whether
+ # it was a build signature, a content signature, or a timestamp
+ # "signature". Since we no longer use build signatures, the
+ # best we can do is look at the length and if it's thirty two,
+ # assume that it was (or might have been) a content signature.
+ # If it was actually a build signature, then it will cause a
+ # rebuild anyway when it doesn't match the new content signature,
+ # but that's probably the best we can do.
+ import SCons.SConsign
+ new_entry = SCons.SConsign.SConsignEntry()
+ new_entry.binfo = self.new_binfo()
+ binfo = new_entry.binfo
+ for attr in self.convert_copy_attrs:
+ try:
+ value = getattr(old_entry, attr)
+ except AttributeError:
+ pass
+ else:
+ setattr(binfo, attr, value)
+ delattr(old_entry, attr)
+ for attr in self.convert_sig_attrs:
+ try:
+ sig_list = getattr(old_entry, attr)
+ except AttributeError:
+ pass
+ else:
+ value = []
+ for sig in sig_list:
+ ninfo = self.new_ninfo()
+ if len(sig) == 32:
+ ninfo.csig = sig
+ else:
+ ninfo.timestamp = sig
+ value.append(ninfo)
+ setattr(binfo, attr, value)
+ delattr(old_entry, attr)
+ return new_entry
+
+ memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
def get_stored_info(self):
try:
- stored = self.dir.sconsign().get_entry(self.name)
+ return self._memo['get_stored_info']
+ except KeyError:
+ pass
+
+ try:
+ sconsign_entry = self.dir.sconsign().get_entry(self.name)
except (KeyError, OSError):
- return self.new_binfo()
+ import SCons.SConsign
+ sconsign_entry = SCons.SConsign.SConsignEntry()
+ sconsign_entry.binfo = self.new_binfo()
+ sconsign_entry.ninfo = self.new_ninfo()
else:
- if not hasattr(stored, 'ninfo'):
- # Transition: The .sconsign file entry has no NodeInfo
- # object, which means it's a slightly older BuildInfo.
- # Copy over the relevant attributes.
- ninfo = stored.ninfo = self.new_ninfo()
- for attr in ninfo.__dict__.keys():
- try:
- setattr(ninfo, attr, getattr(stored, attr))
- except AttributeError:
- pass
- return stored
+ if isinstance(sconsign_entry, FileBuildInfo):
+ # This is a .sconsign file from before the Big Signature
+ # Refactoring; convert it as best we can.
+ sconsign_entry = self.convert_old_entry(sconsign_entry)
+ try:
+ delattr(sconsign_entry.ninfo, 'bsig')
+ except AttributeError:
+ pass
+
+ self._memo['get_stored_info'] = sconsign_entry
+
+ return sconsign_entry
def get_stored_implicit(self):
- binfo = self.get_stored_info()
+ binfo = self.get_stored_info().binfo
binfo.prepare_dependencies()
try: return binfo.bimplicit
except AttributeError: return None
- def rel_path(self, other):
- return self.dir.rel_path(other)
+# def rel_path(self, other):
+# return self.dir.rel_path(other)
def _get_found_includes_key(self, env, scanner, path):
return (id(env), id(scanner), path)
if self.exists():
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
+
+ if csig:
+ ninfo.csig = csig
+
+ ninfo.timestamp = mtime
+ ninfo.size = 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.
+ self.get_binfo().__dict__.update(old.binfo.__dict__)
+
+ self.store_info()
+
+ def find_src_builder(self):
+ if self.rexists():
+ return None
+ scb = self.dir.src_builder()
+ if scb is _null:
+ if diskcheck_sccs(self.dir, self.name):
+ scb = get_DefaultSCCSBuilder()
+ elif diskcheck_rcs(self.dir, self.name):
+ scb = get_DefaultRCSBuilder()
+ else:
+ scb = None
+ if scb is not None:
+ try:
+ b = self.builder
+ except AttributeError:
+ b = None
+ if b is None:
+ self.builder_set(scb)
+ return scb
+
def has_src_builder(self):
"""Return whether this Node has a source builder or not.
try:
scb = self.sbuilder
except AttributeError:
- if self.rexists():
- scb = None
- else:
- scb = self.dir.src_builder()
- if scb is _null:
- if diskcheck_sccs(self.dir, self.name):
- scb = get_DefaultSCCSBuilder()
- elif diskcheck_rcs(self.dir, self.name):
- scb = get_DefaultRCSBuilder()
- else:
- scb = None
- if scb is not None:
- self.builder_set(scb)
- self.sbuilder = scb
+ scb = self.sbuilder = self.find_src_builder()
return not scb is None
def alter_targets(self):
return [], None
return self.fs.build_dir_target_climb(self, self.dir, [self.name])
- def is_pseudo_derived(self):
- return self.has_src_builder()
-
def _rmv_existing(self):
self.clear_memoized_values()
Unlink(self, [], None)
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def make_ready(self):
+ self.has_src_builder()
+ self.get_binfo()
+
def prepare(self):
"""Prepare for this file to be created."""
SCons.Node.Node.prepare(self)
desc = "No drive `%s' for target `%s'." % (drive, self)
raise SCons.Errors.StopError, desc
+ #
+ #
+ #
+
def remove(self):
"""Remove this file."""
if self.exists() or self.islink():
# SIGNATURE SUBSYSTEM
#
- def get_csig(self, calc=None):
+ def get_csig(self):
"""
Generate a node's content signature, the digested signature
of its content.
cache - alternate node to use for the signature cache
returns - the content signature
"""
+ ninfo = self.get_ninfo()
try:
- return self.binfo.ninfo.csig
+ return ninfo.csig
except AttributeError:
pass
- if calc is None:
- calc = self.calculator()
+ 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 = None
+ else:
+ csig = SCons.Util.MD5signature(contents)
- max_drift = self.fs.max_drift
- mtime = self.get_timestamp()
- use_stored = max_drift >= 0 and (time.time() - mtime) > max_drift
+ ninfo.csig = csig
- csig = None
- if use_stored:
- old = self.get_stored_info().ninfo
+ return csig
+
+ #
+ # DECISION SUBSYSTEM
+ #
+
+ def builder_set(self, builder):
+ SCons.Node.Node.builder_set(self, builder)
+ self.changed_since_last_build = self.decide_target
+
+ def changed_content(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+ def changed_state(self, target, prev_ni):
+ return (self.state != SCons.Node.up_to_date)
+
+ def changed_timestamp_then_content(self, target, prev_ni):
+ if not self.changed_timestamp_match(target, prev_ni):
try:
- if old.timestamp and old.csig and old.timestamp == mtime:
- csig = old.csig
+ self.get_ninfo().csig = prev_ni.csig
except AttributeError:
pass
- if csig is None:
- csig = calc.module.signature(self)
+ return False
+ return self.changed_content(target, prev_ni)
- binfo = self.get_binfo()
- ninfo = binfo.ninfo
- ninfo.csig = csig
- ninfo.update(self)
+ def changed_timestamp_newer(self, target, prev_ni):
+ try:
+ return self.get_timestamp() > target.get_timestamp()
+ except AttributeError:
+ return 1
+
+ def changed_timestamp_match(self, target, prev_ni):
+ try:
+ return self.get_timestamp() != prev_ni.timestamp
+ except AttributeError:
+ return 1
- if use_stored:
- self.store_info(binfo)
+ def decide_source(self, target, prev_ni):
+ return target.get_build_env().decide_source(self, target, prev_ni)
- return csig
+ def decide_target(self, target, prev_ni):
+ return target.get_build_env().decide_target(self, target, prev_ni)
- #
- #
- #
+ # Initialize this Node's decider function to decide_source() because
+ # every file is a source file until it has a Builder attached...
+ changed_since_last_build = decide_source
- def is_up_to_date(self, node=None, bi=None):
- """Returns if the node is up-to-date with respect to stored
- BuildInfo. The default is to compare it against our own
- previously stored BuildInfo, but the stored BuildInfo from another
- Node (typically one in a Repository) can be used instead."""
- if bi is None:
- if node is None:
- node = self
- bi = node.get_stored_info()
- new = self.get_binfo()
- return new == bi
-
- def current(self, calc=None):
- self.binfo = self.gen_binfo(calc)
- return self._cur2()
- def _cur2(self):
+ def is_up_to_date(self):
+ T = 0
+ if T: Trace('is_up_to_date(%s):' % self)
if not self.exists():
+ if T: Trace(' not self.exists():')
# The file doesn't exist locally...
r = self.rfile()
if r != self:
# ...but there is one in a Repository...
- if self.is_up_to_date(r):
+ if not self.changed(r):
+ if T: Trace(' changed(%s):' % r)
# ...and it's even up-to-date...
if self._local:
# ...and they'd like a local copy.
LocalCopy(self, r, None)
- self.store_info(self.get_binfo())
+ self.store_info()
+ if T: Trace(' 1\n')
return 1
+ self.changed()
+ if T: Trace(' None\n')
return None
else:
- return self.is_up_to_date()
+ r = self.changed()
+ if T: Trace(' self.exists(): %s\n' % r)
+ return not r
memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
def rstr(self):
return str(self.rfile())
+ def get_cachedir_csig(self):
+ """
+ Fetch a Node's content signature for purposes of computing
+ another Node's cachesig.
+
+ This is a wrapper around the normal get_csig() method that handles
+ the somewhat obscure case of using CacheDir with the -n option.
+ Any files that don't exist would normally be "built" by fetching
+ them from the cache, but the normal get_csig() method will try
+ to open up the local file, which doesn't exist because the -n
+ option meant we didn't actually pull the file from cachedir.
+ But since the file *does* actually exist in the cachedir, we
+ can use its contents for the csig.
+ """
+ try:
+ return self.cachedir_csig
+ except AttributeError:
+ pass
+
+ cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
+ if not self.exists() and cachefile and os.path.exists(cachefile):
+ contents = open(cachefile, 'rb').read()
+ self.cachedir_csig = SCons.Util.MD5signature(contents)
+ else:
+ self.cachedir_csig = self.get_csig()
+ return self.cachedir_csig
+
def get_cachedir_bsig(self):
- import SCons.Sig.MD5
- ninfo = self.get_binfo().ninfo
- if not hasattr(ninfo, 'bsig'):
- import SCons.Errors
- raise SCons.Errors.InternalError, "cachepath(%s) found no bsig" % self.path
- elif ninfo.bsig is None:
- import SCons.Errors
- raise SCons.Errors.InternalError, "cachepath(%s) found a bsig of None" % self.path
+ try:
+ return self.cachesig
+ except AttributeError:
+ pass
+
# Add the path to the cache signature, because multiple
# targets built by the same action will all have the same
# build signature, and we have to differentiate them somehow.
- return SCons.Sig.MD5.collect([ninfo.bsig, self.path])
+ children = self.children()
+ sigs = map(lambda n: n.get_cachedir_csig(), children)
+ executor = self.get_executor()
+ sigs.append(SCons.Util.MD5signature(executor.get_contents()))
+ sigs.append(self.path)
+ self.cachesig = SCons.Util.MD5collect(sigs)
+ return self.cachesig
default_fs = None
import SCons.Errors
import SCons.Node.FS
+import SCons.Util
import SCons.Warnings
built_it = None
pass
class Action:
- def __call__(self, targets, sources, env, errfunc, **kw):
+ def __call__(self, targets, sources, env, **kw):
global built_it
if kw.get('execute', 1):
built_it = 1
return 0
def show(self, string):
pass
+ def get_contents(self, target, source, env):
+ return ""
+ def genstring(self, target, source, env):
+ return ""
def strfunction(self, targets, sources, env):
return ""
+ def get_implicit_deps(self, target, source, env):
+ return []
class Builder:
def __init__(self, factory, action=Action()):
class MkdirAction(Action):
def __init__(self, dir_made):
self.dir_made = dir_made
- def __call__(self, target, source, env, errfunc):
+ def __call__(self, target, source, env):
self.dir_made.extend(target)
save_Link = SCons.Node.FS.Link
"""Test FileNodeInfo initialization"""
fff = self.fs.File('fff')
ni = SCons.Node.FS.FileNodeInfo(fff)
- assert hasattr(ni, 'timestamp')
- assert hasattr(ni, 'size')
-
- def test___cmp__(self):
- """Test comparing File.NodeInfo objects"""
- f1 = self.fs.File('f1')
- f2 = self.fs.File('f2')
+ assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
- ni1 = SCons.Node.FS.FileNodeInfo(f1)
- ni2 = SCons.Node.FS.FileNodeInfo(f2)
+ def test_update(self):
+ """Test updating a File.NodeInfo with on-disk information"""
+ test = self.test
+ fff = self.fs.File('fff')
- msg = "cmp(%s, %s) returned %s, not %s"
+ ni = SCons.Node.FS.FileNodeInfo(fff)
- c = cmp(ni1, ni2)
- assert c == 1, msg % (ni1, ni2, c, 1)
+ test.write('fff', "fff\n")
- ni1.bsig = 777
- c = cmp(ni1, ni2)
- assert c == 1, msg % (ni1.bsig, ni2, c, 1)
+ st = os.stat('fff')
- ni2.bsig = 666
- c = cmp(ni1, ni2)
- assert c == 1, msg % (ni1.bsig, ni2.bsig, c, 1)
+ ni.update(fff)
- ni2.bsig = 777
- c = cmp(ni1, ni2)
- assert c == 0, msg % (ni1.bsig, ni2.bsig, c, 0)
+ assert hasattr(ni, 'timestamp')
+ assert hasattr(ni, 'size')
- ni2.bsig = 888
- c = cmp(ni1, ni2)
- assert c == -1, msg % (ni1.bsig, ni2.bsig, c, -1)
+ ni.timestamp = 0
+ ni.size = 0
- def test_update(self):
- """Test updating a File.NodeInfo with on-disk information"""
- test = self.test
- fff = self.fs.File('fff')
+ ni.update(fff)
- ni = SCons.Node.FS.FileNodeInfo(fff)
+ mtime = st[stat.ST_MTIME]
+ assert ni.timestamp == mtime, (ni.timestamp, mtime)
+ size = st[stat.ST_SIZE]
+ assert ni.size == size, (ni.size, size)
import time
time.sleep(2)
- test.write('fff', "fff\n")
+ test.write('fff', "fff longer size, different time stamp\n")
st = os.stat('fff')
size = st[stat.ST_SIZE]
assert ni.size != size, (ni.size, size)
- fff.clear()
- ni.update(fff)
+ #fff.clear()
+ #ni.update(fff)
- st = os.stat('fff')
+ #st = os.stat('fff')
- mtime = st[stat.ST_MTIME]
- assert ni.timestamp == mtime, (ni.timestamp, mtime)
- size = st[stat.ST_SIZE]
- assert ni.size == size, (ni.size, size)
+ #mtime = st[stat.ST_MTIME]
+ #assert ni.timestamp == mtime, (ni.timestamp, mtime)
+ #size = st[stat.ST_SIZE]
+ #assert ni.size == size, (ni.size, size)
class FileBuildInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test File.BuildInfo initialization"""
fff = self.fs.File('fff')
- bi = SCons.Node.FS.BuildInfo(fff)
- assert bi.node is fff, bi.node
+ bi = SCons.Node.FS.FileBuildInfo(fff)
+ assert bi, bi
def test_convert_to_sconsign(self):
"""Test converting to .sconsign file format"""
def test_format(self):
"""Test the format() method"""
f1 = self.fs.File('f1')
- bi1 = SCons.Node.FS.BuildInfo(f1)
+ bi1 = SCons.Node.FS.FileBuildInfo(f1)
s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1'))
- s1sig.a = 1
+ s1sig.csig = 1
d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2'))
- d1sig.a = 2
+ d1sig.timestamp = 2
i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3'))
- i1sig.a = 3
+ i1sig.size = 3
bi1.bsources = [self.fs.File('s1')]
bi1.bdepends = [self.fs.File('d1')]
bi1.bsourcesigs = [s1sig]
bi1.bdependsigs = [d1sig]
bi1.bimplicitsigs = [i1sig]
+ bi1.bact = 'action'
+ bi1.bactsig = 'actionsig'
expect_lines = [
- 'None 0',
- 's1: 1 None 0',
- 'd1: 2 None 0',
- 'i1: 3 None 0',
+ 's1: 1 None None',
+ 'd1: None 2 None',
+ 'i1: None None 3',
+ 'actionsig [action]',
]
expect = string.join(expect_lines, '\n')
format = bi1.format()
- assert format == expect, (repr(format), repr(expect))
+ assert format == expect, (repr(expect), repr(format))
class FSTestCase(_tempdirTestCase):
def test_runTest(self):
except TypeError:
pass
else:
- assert 0
+ raise Exception, "did not catch expected TypeError"
assert x1.Entry(x4) == x4
try:
except TypeError:
pass
else:
- assert 0
+ raise Exception, "did not catch expected TypeError"
x6 = x1.File(x6)
assert isinstance(x6, SCons.Node.FS.File)
except:
raise
- # Test that just specifying the drive works to identify
- # its root directory.
- p = os.path.abspath(test.workpath('root_file'))
+ # Test that just specifying the drive works to identify
+ # its root directory.
+ p = os.path.abspath(test.workpath('root_file'))
+ drive, path = os.path.splitdrive(p)
+ if drive:
+ # The assert below probably isn't correct for the general
+ # case, but it works for Windows, which covers a lot
+ # of ground...
+ dir = fs.Dir(drive)
+ assert str(dir) == drive + os.sep, str(dir)
+
+ # Make sure that lookups with and without the drive are
+ # equivalent.
+ p = os.path.abspath(test.workpath('some/file'))
drive, path = os.path.splitdrive(p)
- if drive:
- # The assert below probably isn't correct for the general
- # case, but it works for Windows, which covers a lot
- # of ground...
- dir = fs.Dir(drive)
- assert str(dir) == drive + os.sep, str(dir)
+
+ e1 = fs.Entry(p)
+ e2 = fs.Entry(path)
+ assert e1 is e2, (e1, e2)
+ assert str(e1) is str(e2), (str(e1), str(e2))
# Test for a bug in 0.04 that did not like looking up
# dirs with a trailing slash on Windows.
except SCons.Errors.UserError:
pass
else:
- raise TestFailed, "did not catch expected UserError"
+ raise Exception, "did not catch expected UserError"
nonexistent(fs.Entry, 'nonexistent')
nonexistent(fs.Entry, 'nonexistent/foo')
f = fs.File('f_local')
assert f._local == 0
- #XXX test current() for directories
+ #XXX test_is_up_to_date() for directories
- #XXX test sconsign() for directories
+ #XXX test_sconsign() for directories
- #XXX test set_signature() for directories
+ #XXX test_set_signature() for directories
- #XXX test build() for directories
+ #XXX test_build() for directories
- #XXX test root()
+ #XXX test_root()
# test Entry.get_contents()
e = fs.Entry('does_not_exist')
exc_caught = 1
assert exc_caught, "Should have caught a TypeError"
- # XXX test calc_signature()
-
- # XXX test current()
+ # XXX test_is_up_to_date()
d = fs.Dir('dir')
r = d.remove()
subdir = fs.Dir('subdir')
fs.chdir(subdir, change_os_dir=1)
- path, dir = fs._transformPath('#build/file', subdir)
- self.fs._doLookup(SCons.Node.FS.File, path, dir)
+ self.fs._lookup('#build/file', subdir, SCons.Node.FS.File)
def test_above_root(self):
"""Testing looking up a path above the root directory"""
above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
above = d2.Dir(above_path)
- def test_rel_path(self):
+ # Note that the rel_path() method is not used right now, but we're
+ # leaving it commented out and disabling the unit here because
+ # it would be a shame to have to recreate the logic (or remember
+ # that it's buried in a long-past code checkin) if we ever need to
+ # resurrect it.
+
+ def DO_NOT_test_rel_path(self):
"""Test the rel_path() method"""
test = self.test
fs = self.fs
os.path.join('ddd', 'f2'),
os.path.join('ddd', 'f3')], kids
+ def test_implicit_re_scans(self):
+ """Test that adding entries causes a directory to be re-scanned
+ """
+
+ fs = self.fs
+
+ dir = fs.Dir('ddd')
+
+ fs.File(os.path.join('ddd', 'f1'))
+ dir.scan()
+ kids = map(lambda x: x.path, dir.children())
+ kids.sort()
+ assert kids == [os.path.join('ddd', 'f1')], kids
+
+ fs.File(os.path.join('ddd', 'f2'))
+ dir.scan()
+ kids = map(lambda x: x.path, dir.children())
+ kids.sort()
+ assert kids == [os.path.join('ddd', 'f1'),
+ os.path.join('ddd', 'f2')], kids
+
def test_entry_exists_on_disk(self):
"""Test the Dir.entry_exists_on_disk() method
"""
derived_f = src0.File('derived-f')
derived_f.is_derived = return_true
- pseudo_f = src0.File('pseudo-f')
- pseudo_f.is_pseudo_derived = return_true
exists_f = src0.File('exists-f')
exists_f.exists = return_true
derived_e = src0.Entry('derived-e')
derived_e.is_derived = return_true
- pseudo_e = src0.Entry('pseudo-e')
- pseudo_e.is_pseudo_derived = return_true
exists_e = src0.Entry('exists-e')
exists_e.exists = return_true
n = src0.srcdir_find_file('derived-f')
check(n, ['src0/derived-f', 'src0'])
- n = src0.srcdir_find_file('pseudo-f')
- check(n, ['src0/pseudo-f', 'src0'])
n = src0.srcdir_find_file('exists-f')
check(n, ['src0/exists-f', 'src0'])
n = src0.srcdir_find_file('on-disk-f1')
n = src0.srcdir_find_file('derived-e')
check(n, ['src0/derived-e', 'src0'])
- n = src0.srcdir_find_file('pseudo-e')
- check(n, ['src0/pseudo-e', 'src0'])
n = src0.srcdir_find_file('exists-e')
check(n, ['src0/exists-e', 'src0'])
n = src0.srcdir_find_file('on-disk-e1')
n = bld0.srcdir_find_file('derived-f')
check(n, ['src0/derived-f', 'bld0'])
- n = bld0.srcdir_find_file('pseudo-f')
- check(n, ['src0/pseudo-f', 'bld0'])
n = bld0.srcdir_find_file('exists-f')
check(n, ['src0/exists-f', 'bld0'])
n = bld0.srcdir_find_file('on-disk-f2')
n = bld0.srcdir_find_file('derived-e')
check(n, ['src0/derived-e', 'bld0'])
- n = bld0.srcdir_find_file('pseudo-e')
- check(n, ['src0/pseudo-e', 'bld0'])
n = bld0.srcdir_find_file('exists-e')
check(n, ['src0/exists-e', 'bld0'])
n = bld0.srcdir_find_file('on-disk-e2')
derived_f = src1.File('derived-f')
derived_f.is_derived = return_true
- pseudo_f = src1.File('pseudo-f')
- pseudo_f.is_pseudo_derived = return_true
exists_f = src1.File('exists-f')
exists_f.exists = return_true
derived_e = src1.Entry('derived-e')
derived_e.is_derived = return_true
- pseudo_e = src1.Entry('pseudo-e')
- pseudo_e.is_pseudo_derived = return_true
exists_e = src1.Entry('exists-e')
exists_e.exists = return_true
n = src1.srcdir_find_file('derived-f')
check(n, ['src1/derived-f', 'src1'])
- n = src1.srcdir_find_file('pseudo-f')
- check(n, ['src1/pseudo-f', 'src1'])
n = src1.srcdir_find_file('exists-f')
check(n, ['src1/exists-f', 'src1'])
n = src1.srcdir_find_file('on-disk-f1')
n = src1.srcdir_find_file('derived-e')
check(n, ['src1/derived-e', 'src1'])
- n = src1.srcdir_find_file('pseudo-e')
- check(n, ['src1/pseudo-e', 'src1'])
n = src1.srcdir_find_file('exists-e')
check(n, ['src1/exists-e', 'src1'])
n = src1.srcdir_find_file('on-disk-e1')
n = bld1.srcdir_find_file('derived-f')
check(n, ['bld1/derived-f', 'src1'])
- n = bld1.srcdir_find_file('pseudo-f')
- check(n, ['bld1/pseudo-f', 'src1'])
n = bld1.srcdir_find_file('exists-f')
check(n, ['bld1/exists-f', 'src1'])
n = bld1.srcdir_find_file('on-disk-f2')
n = bld1.srcdir_find_file('derived-e')
check(n, ['bld1/derived-e', 'src1'])
- n = bld1.srcdir_find_file('pseudo-e')
- check(n, ['bld1/pseudo-e', 'src1'])
n = bld1.srcdir_find_file('exists-e')
check(n, ['bld1/exists-e', 'src1'])
n = bld1.srcdir_find_file('on-disk-e2')
test.subdir('e5d')
test.write('e5f', "e5f\n")
- e5f = fs.Entry('e5f')
- sig = e5f.calc_signature(MyCalc(666))
- assert e5f.__class__ is SCons.Node.FS.File, e5f.__class__
- # This node has no builder, so it just calculates the
- # signature once: the source content signature.
- assert sig == 888, sig
-
- e5n = fs.Entry('e5n')
- sig = e5n.calc_signature(MyCalc(777))
- assert e5n.__class__ is SCons.Node.FS.File, e5n.__class__
- # Doesn't exist, no sources, and no builder: no sig
- assert sig is None, sig
-
def test_Entry_Entry_lookup(self):
"""Test looking up an Entry within another Entry"""
self.fs.Entry('#topdir')
finally:
test.unlink(["rep3", "contents"])
- #def test calc_signature(self):
-
- #def test current(self):
+ #def test_is_up_to_date(self):
d = fs.Dir('sub')
f = fs.File('file1', d)
bi = f.get_stored_info()
- assert bi.ninfo.timestamp == 0, bi.ninfo.timestamp
- assert bi.ninfo.size == None, bi.ninfo.size
+ assert hasattr(bi, 'ninfo')
class MySConsign:
class Null:
class MkdirAction(Action):
def __init__(self, dir_made):
self.dir_made = dir_made
- def __call__(self, target, source, env, errfunc):
+ def __call__(self, target, source, env):
self.dir_made.extend(target)
dir_made = []
dir = fs.Dir("dir")
dir.prepare()
+
+
class SConstruct_dirTestCase(unittest.TestCase):
def runTest(self):
"""Test setting the SConstruct directory"""
fs.set_SConstruct_dir(fs.Dir('xxx'))
assert fs.SConstruct_dir.path == 'xxx'
+
+
+class CacheDirTestCase(unittest.TestCase):
+
+ def test_get_cachedir_csig(self):
+ fs = SCons.Node.FS.FS()
+
+ f9 = fs.File('f9')
+ r = f9.get_cachedir_csig()
+ assert r == 'd41d8cd98f00b204e9800998ecf8427e', r
+
+
+
class clearTestCase(unittest.TestCase):
def runTest(self):
"""Test clearing FS nodes of cached data."""
assert not f.rexists()
assert str(f) == test.workpath('f'), str(f)
+
+
class disambiguateTestCase(unittest.TestCase):
def runTest(self):
"""Test calling the disambiguate() method."""
f = fs.File('f')
f.postprocess()
+
+
class SpecialAttrTestCase(unittest.TestCase):
def runTest(self):
"""Test special attributes of file nodes."""
assert s == os.path.normpath('baz/bar/baz.blat'), s
assert f.srcpath.is_literal(), f.srcpath
g = f.srcpath.get()
- assert isinstance(g, SCons.Node.FS.Entry), g.__class__
+ assert isinstance(g, SCons.Node.FS.File), g.__class__
s = str(f.srcdir)
assert s == os.path.normpath('baz/bar'), s
caught = 1
assert caught, "did not catch expected AttributeError"
+
+
class SaveStringsTestCase(unittest.TestCase):
def runTest(self):
"""Test caching string values of nodes."""
expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b'])
assert s == expect, 'node str() not cached: %s'%s
+
+
if __name__ == "__main__":
suite = unittest.TestSuite()
suite.addTest(BuildDirTestCase())
suite.addTest(SaveStringsTestCase())
tclasses = [
BaseTestCase,
+ CacheDirTestCase,
DirTestCase,
DirBuildInfoTestCase,
DirNodeInfoTestCase,
def __init__(self):
self.order = 0
- def __call__(self, target, source, env, errfunc):
+ def __call__(self, target, source, env):
global built_it, built_target, built_source, built_args, built_order
built_it = 1
built_target = target
self.order = built_order
return 0
+ def get_implicit_deps(self, target, source, env):
+ return []
+
class MyExecutor:
def __init__(self, env=None, targets=[], sources=[]):
self.env = env
class MyListAction(MyActionBase):
def __init__(self, list):
self.list = list
- def __call__(self, target, source, env, errfunc):
+ def __call__(self, target, source, env):
for A in self.list:
- A(target, source, env, errfunc)
+ A(target, source, env)
class Environment:
def __init__(self, **kw):
return apply(Environment, (), d)
def _update(self, dict):
self._dict.update(dict)
- def get_calculator(self):
- return SCons.Sig.default_calc
def get_factory(self, factory):
return factory or MyNode
def get_scanner(self, scanner_key):
class NodeInfoBaseTestCase(unittest.TestCase):
- def test_prepare_dependencies(self):
- """Test that we have a prepare_dependencies() method"""
- ni = SCons.Node.NodeInfoBase(SCons.Node.Node())
- ni.prepare_dependencies()
-
- def test___cmp__(self):
- """Test comparing NodeInfoBase objects"""
- ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
- ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node())
-
- assert ni1 == ni2, "%s != %s" % (ni1.__dict__, ni2.__dict__)
-
- ni1.foo = 777
- assert ni1 != ni2, "%s == %s" % (ni1.__dict__, ni2.__dict__)
-
- ni2.foo = 888
- assert ni1 != ni2, "%s == %s" % (ni1.__dict__, ni2.__dict__)
-
- ni1.foo = 888
- assert ni1 == ni2, "%s != %s" % (ni1.__dict__, ni2.__dict__)
-
def test_merge(self):
"""Test merging NodeInfoBase attributes"""
ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
ni2.a3 = 333
ni1.merge(ni2)
- assert ni1.__dict__ == {'a1':1, 'a2':222, 'a3':333}, ni1.__dict__
+ expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1}
+ assert ni1.__dict__ == expect, ni1.__dict__
def test_update(self):
"""Test the update() method"""
ni1.zzz = 'z'
f = ni1.format()
- assert f == 'x y z', f
+ assert f == ['1', 'x', 'y', 'z'], f
ni1.field_list = ['xxx', 'zzz', 'aaa']
f = ni1.format()
- assert f == 'x z None', f
+ assert f == ['x', 'z', 'None'], f
def test___init__(self):
"""Test BuildInfoBase initialization"""
- bi = SCons.Node.BuildInfoBase(SCons.Node.Node())
- assert hasattr(bi, 'ninfo')
-
- class MyNode(SCons.Node.Node):
- def NodeInfo(self, node):
- return 'ninfo initialization'
- bi = SCons.Node.BuildInfoBase(MyNode())
- assert bi.ninfo == 'ninfo initialization', bi.ninfo
-
- def test___cmp__(self):
- """Test comparing BuildInfoBase objects"""
- bi1 = SCons.Node.BuildInfoBase(SCons.Node.Node())
- bi2 = SCons.Node.BuildInfoBase(SCons.Node.Node())
-
- assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
-
- bi1.ninfo.foo = 777
- assert bi1 != bi2, "%s == %s" % (bi1.__dict__, bi2.__dict__)
-
- bi2.ninfo.foo = 888
- assert bi1 != bi2, "%s == %s" % (bi1.__dict__, bi2.__dict__)
-
- bi1.ninfo.foo = 888
- assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
-
- bi1.foo = 999
- assert bi1 == bi2, "%s != %s" % (bi1.__dict__, bi2.__dict__)
+ n = SCons.Node.Node()
+ bi = SCons.Node.BuildInfoBase(n)
+ assert bi
def test_merge(self):
"""Test merging BuildInfoBase attributes"""
- bi1 = SCons.Node.BuildInfoBase(SCons.Node.Node())
- bi2 = SCons.Node.BuildInfoBase(SCons.Node.Node())
+ n1 = SCons.Node.Node()
+ bi1 = SCons.Node.BuildInfoBase(n1)
+ n2 = SCons.Node.Node()
+ bi2 = SCons.Node.BuildInfoBase(n2)
bi1.a1 = 1
bi1.a2 = 2
bi2.a2 = 222
bi2.a3 = 333
- bi1.ninfo.a4 = 4
- bi1.ninfo.a5 = 5
- bi2.ninfo.a5 = 555
- bi2.ninfo.a6 = 666
-
bi1.merge(bi2)
assert bi1.a1 == 1, bi1.a1
assert bi1.a2 == 222, bi1.a2
assert bi1.a3 == 333, bi1.a3
- assert bi1.ninfo.a4 == 4, bi1.ninfo.a4
- assert bi1.ninfo.a5 == 555, bi1.ninfo.a5
- assert bi1.ninfo.a6 == 666, bi1.ninfo.a6
-
class NodeTestCase(unittest.TestCase):
def test_built(self):
"""Test the built() method"""
+ class SubNodeInfo(SCons.Node.NodeInfoBase):
+ def update(self, node):
+ self.updated = 1
class SubNode(SCons.Node.Node):
def clear(self):
self.cleared = 1
n = SubNode()
+ n.ninfo = SubNodeInfo(n)
n.built()
assert n.cleared, n.cleared
+ assert n.ninfo.updated, n.ninfo.cleared
def test_retrieve_from_cache(self):
"""Test the base retrieve_from_cache() method"""
assert t == [], t
assert m == None, m
- def test_current(self):
- """Test the default current() method
+ def test_is_up_to_date(self):
+ """Test the default is_up_to_date() method
"""
node = SCons.Node.Node()
- assert node.current() is None
+ assert node.is_up_to_date() is None
def test_children_are_up_to_date(self):
"""Test the children_are_up_to_date() method used by subclasses
n1 = SCons.Node.Node()
n2 = SCons.Node.Node()
- calc = Calculator(111)
-
- n1.add_source(n2)
- assert n1.children_are_up_to_date(calc), "expected up to date"
+ n1.add_source([n2])
+ assert n1.children_are_up_to_date(), "expected up to date"
n2.set_state(SCons.Node.executed)
- assert not n1.children_are_up_to_date(calc), "expected not up to date"
+ assert not n1.children_are_up_to_date(), "expected not up to date"
n2.set_state(SCons.Node.up_to_date)
- assert n1.children_are_up_to_date(calc), "expected up to date"
+ assert n1.children_are_up_to_date(), "expected up to date"
n1.always_build = 1
- assert not n1.children_are_up_to_date(calc), "expected not up to date"
+ assert not n1.children_are_up_to_date(), "expected not up to date"
def test_env_set(self):
"""Test setting a Node's Environment
a = node.builder.get_actions()
assert isinstance(a[0], MyAction), a[0]
- def test_get_bsig(self):
- """Test generic build signature calculation
+ def test_get_csig(self):
+ """Test generic content signature calculation
"""
node = SCons.Node.Node()
- result = node.get_bsig(Calculator(222))
- assert result == 222, result
- result = node.get_bsig(Calculator(333))
- assert result == 222, result
+ node.get_contents = lambda: 444
+ result = node.get_csig()
+ assert result == '550a141f12de6341fba65b0ad0433500', result
- def test_get_csig(self):
- """Test generic content signature calculation
+ def test_get_cachedir_csig(self):
+ """Test content signature calculation for CacheDir
"""
node = SCons.Node.Node()
- result = node.get_csig(Calculator(444))
- assert result == 444, result
- result = node.get_csig(Calculator(555))
- assert result == 444, result
+ node.get_contents = lambda: 555
+ result = node.get_cachedir_csig()
+ assert result == '15de21c670ae7c3f6f3f1f37029303c9', result
def test_get_binfo(self):
"""Test fetching/creating a build information structure
binfo = node.get_binfo()
assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo
- node.binfo = 777
- binfo = node.get_binfo()
- assert binfo == 777, binfo
-
- def test_gen_binfo(self):
- """Test generating a build information structure
- """
node = SCons.Node.Node()
d = SCons.Node.Node()
+ d.get_ninfo().csig = 777
i = SCons.Node.Node()
+ i.get_ninfo().csig = 888
node.depends = [d]
node.implicit = [i]
- node.gen_binfo(Calculator(666))
- binfo = node.binfo
+
+ binfo = node.get_binfo()
assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo
assert hasattr(binfo, 'bsources')
assert hasattr(binfo, 'bsourcesigs')
assert hasattr(binfo, 'bdependsigs')
assert binfo.bimplicit == [i]
assert hasattr(binfo, 'bimplicitsigs')
- assert binfo.ninfo.bsig == 1998, binfo.ninfo.bsig
def test_explain(self):
"""Test explaining why a Node must be rebuilt
class testNode2(SCons.Node.Node):
def __str__(self): return 'null_binfo'
+ class FS:
+ pass
node = testNode2()
+ node.fs = FS()
+ node.fs.Top = SCons.Node.Node()
result = node.explain()
assert result == None, result
def get_null_info():
- class Null_BInfo:
- def prepare_dependencies(self):
- pass
- return Null_BInfo()
+ class Null_SConsignEntry:
+ class Null_BuildInfo:
+ def prepare_dependencies(self):
+ pass
+ binfo = Null_BuildInfo()
+ return Null_SConsignEntry()
node.get_stored_info = get_null_info
#see above: node.__str__ = lambda: 'null_binfo'
def test_store_info(self):
"""Test calling the method to store build information
"""
- class Entry:
- pass
node = SCons.Node.Node()
- node.store_info(Entry())
+ node.store_info()
def test_get_stored_info(self):
"""Test calling the method to fetch stored build information
five = SCons.Node.Node()
six = SCons.Node.Node()
- node.add_dependency(zero)
+ node.add_dependency([zero])
assert node.depends == [zero]
node.add_dependency([one])
assert node.depends == [zero, one]
five = SCons.Node.Node()
six = SCons.Node.Node()
- node.add_source(zero)
+ node.add_source([zero])
assert node.sources == [zero]
node.add_source([one])
assert node.sources == [zero, one]
five = SCons.Node.Node()
six = SCons.Node.Node()
- node.add_ignore(zero)
+ node.add_ignore([zero])
assert node.ignore == [zero]
node.add_ignore([one])
assert node.ignore == [zero, one]
# if the stored dependencies need recalculation.
class StoredNode(MyNode):
def get_stored_implicit(self):
- return ['implicit1', 'implicit2']
-
- class NotCurrent:
- def current(self, node, sig):
- return None
- def bsig(self, node):
- return 0
-
- import SCons.Sig
+ return [MyNode('implicit1'), MyNode('implicit2')]
- save_default_calc = SCons.Sig.default_calc
save_implicit_cache = SCons.Node.implicit_cache
save_implicit_deps_changed = SCons.Node.implicit_deps_changed
save_implicit_deps_unchanged = SCons.Node.implicit_deps_unchanged
- SCons.Sig.default_calc = NotCurrent()
SCons.Node.implicit_cache = 1
SCons.Node.implicit_deps_changed = None
SCons.Node.implicit_deps_unchanged = None
assert sn.children() == [], sn.children()
finally:
- SCons.Sig.default_calc = save_default_calc
SCons.Node.implicit_cache = save_implicit_cache
SCons.Node.implicit_deps_changed = save_implicit_deps_changed
SCons.Node.implicit_deps_unchanged = save_implicit_deps_unchanged
n.clear()
- assert not hasattr(n, 'binfo'), n.bsig
assert n.includes is None, n.includes
assert n.found_includes == {}, n.found_includes
- assert n.implicit is None, n.implicit
assert x.cleaned_up
def test_get_subst_proxy(self):
import SCons.Node
class ValueNodeInfo(SCons.Node.NodeInfoBase):
- pass
+ current_version_id = 1
+
+ field_list = ['csig']
+
+ def str_to_node(self, s):
+ return Value(s)
class ValueBuildInfo(SCons.Node.BuildInfoBase):
- pass
+ current_version_id = 1
class Value(SCons.Node.Node):
"""A class for Python variables, typically passed on the command line
def __str__(self):
return repr(self.value)
+ def make_ready(self):
+ self.get_csig()
+
def build(self, **kw):
if not hasattr(self, 'built_value'):
apply (SCons.Node.Node.build, (self,), kw)
- current = SCons.Node.Node.children_are_up_to_date
+ is_up_to_date = SCons.Node.Node.children_are_up_to_date
def is_under(self, dir):
# Make Value nodes get built regardless of
contents = contents + kid.get_contents()
return contents
+ def changed_since_last_build(self, target, prev_ni):
+ cur_csig = self.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
def get_csig(self, calc=None):
"""Because we're a Python value node and don't have a real
timestamp, we get to ignore the calculator and just use the
value contents."""
try:
- binfo = self.binfo
- except AttributeError:
- binfo = self.binfo = self.new_binfo()
- try:
- return binfo.ninfo.csig
+ return self.ninfo.csig
except AttributeError:
- binfo.ninfo.csig = self.get_contents()
- self.store_info(binfo)
- return binfo.ninfo.csig
+ pass
+ contents = self.get_contents()
+ self.get_ninfo().csig = contents
+ return contents
"""Test "building" a Value Node
"""
class fake_executor:
- def __call__(self, node, exitstatfunc):
+ def __call__(self, node):
node.write('faked')
v1 = SCons.Node.Python.Value('b', 'built')
from SCons.Debug import logInstanceCreation
import SCons.Executor
import SCons.Memoize
-import SCons.SConsign
import SCons.Util
+from SCons.Debug import Trace
+
+def classname(obj):
+ return string.split(str(obj.__class__), '.')[-1]
+
# Node states
#
# These are in "priority" order, so that the maximum value for any
Node subclasses should subclass NodeInfoBase to provide their own
logic for dealing with their own Node-specific signature information.
"""
+ current_version_id = 1
def __init__(self, node):
- """A null initializer so that subclasses have a superclass
- initialization method to call for future use.
- """
- pass
- def __cmp__(self, other):
- return cmp(self.__dict__, other.__dict__)
+ # Create an object attribute from the class attribute so it ends up
+ # in the pickled data in the .sconsign file.
+ self._version_id = self.current_version_id
def update(self, node):
- pass
- def merge(self, other):
- for key, val in other.__dict__.items():
- self.__dict__[key] = val
- def prepare_dependencies(self):
- pass
- def format(self):
try:
field_list = self.field_list
except AttributeError:
- field_list = self.__dict__.keys()
- field_list.sort()
+ return
+ for f in field_list:
+ try:
+ delattr(self, f)
+ except AttributeError:
+ pass
+ try:
+ func = getattr(node, 'get_' + f)
+ except AttributeError:
+ pass
+ else:
+ setattr(self, f, func())
+ def convert(self, node, val):
+ pass
+ def merge(self, other):
+ self.__dict__.update(other.__dict__)
+ def format(self, field_list=None, names=0):
+ if field_list is None:
+ try:
+ field_list = self.field_list
+ except AttributeError:
+ field_list = self.__dict__.keys()
+ field_list.sort()
fields = []
for field in field_list:
try:
f = getattr(self, field)
except AttributeError:
f = None
- fields.append(str(f))
- return string.join(fields, " ")
+ f = str(f)
+ if names:
+ f = field + ': ' + f
+ fields.append(f)
+ return fields
class BuildInfoBase:
"""
- The generic base clasee for build information for a Node.
+ The generic base class for build information for a Node.
This is what gets stored in a .sconsign file for each target file.
It contains a NodeInfo instance for this node (signature information
generic build stuff we have to track: sources, explicit dependencies,
implicit dependencies, and action information.
"""
+ current_version_id = 1
def __init__(self, node):
- self.ninfo = node.NodeInfo(node)
+ # Create an object attribute from the class attribute so it ends up
+ # in the pickled data in the .sconsign file.
+ self._version_id = self.current_version_id
self.bsourcesigs = []
self.bdependsigs = []
self.bimplicitsigs = []
self.bactsig = None
- def __cmp__(self, other):
- return cmp(self.ninfo, other.ninfo)
def merge(self, other):
- for key, val in other.__dict__.items():
- try:
- merge = self.__dict__[key].merge
- except (AttributeError, KeyError):
- self.__dict__[key] = val
- else:
- merge(val)
+ self.__dict__.update(other.__dict__)
class Node:
"""The base Node class, for entities that we know how to
def get_suffix(self):
return ''
+ memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
+
def get_build_env(self):
"""Fetch the appropriate Environment to build this node.
"""
- return self.get_executor().get_build_env()
+ try:
+ return self._memo['get_build_env']
+ except KeyError:
+ pass
+ result = self.get_executor().get_build_env()
+ self._memo['get_build_env'] = result
+ return result
def get_build_scanner_path(self, scanner):
"""Fetch the appropriate scanner path for this node."""
"""
return 0
+ #
+ # Taskmaster interface subsystem
+ #
+
+ def make_ready(self):
+ """Get a Node ready for evaluation.
+
+ This is called before the Taskmaster decides if the Node is
+ up-to-date or not. Overriding this method allows for a Node
+ subclass to be disambiguated if necessary, or for an implicit
+ source builder to be attached.
+ """
+ pass
+
+ def prepare(self):
+ """Prepare for this Node to be built.
+
+ This is called after the Taskmaster has decided that the Node
+ is out-of-date and must be rebuilt, but before actually calling
+ the method to build the Node.
+
+ This default implemenation checks that all children either exist
+ or are derived, and initializes the BuildInfo structure that
+ will hold the information about how this node is, uh, built.
+
+ Overriding this method allows for for a Node subclass to remove
+ the underlying file from the file system. Note that subclass
+ methods should call this base class method to get the child
+ check and the BuildInfo structure.
+ """
+ l = self.depends
+ if not self.implicit is None:
+ l = l + self.implicit
+ missing_sources = self.get_executor().get_missing_sources() \
+ + filter(lambda c: c.missing(), l)
+ if missing_sources:
+ desc = "Source `%s' not found, needed by target `%s'." % (missing_sources[0], self)
+ raise SCons.Errors.StopError, desc
+
+ self.binfo = self.get_binfo()
+
def build(self, **kw):
"""Actually build the node.
+ This is called by the Taskmaster after it's decided that the
+ Node is out-of-date and must be rebuilt, and after the prepare()
+ method has gotten everything, uh, prepared.
+
This method is called from multiple threads in a parallel build,
- so only do thread safe stuff here. Do thread unsafe stuff in
- built().
+ so only do thread safe stuff here. Do thread unsafe stuff
+ in built().
+
"""
- def exitstatfunc(stat, node=self):
- if stat:
- msg = "Error %d" % stat
- raise SCons.Errors.BuildError(node=node, errstr=msg)
executor = self.get_executor()
- apply(executor, (self, exitstatfunc), kw)
+ stat = apply(executor, (self,), kw)
+ if stat:
+ msg = "Error %d" % stat
+ raise SCons.Errors.BuildError(node=self, errstr=msg)
def built(self):
"""Called just after this node is successfully built."""
parent.implicit = None
parent.del_binfo()
+ self.clear()
+
+ self.ninfo.update(self)
+
+ def visited(self):
+ """Called just after this node has been visited (with or
+ without a build)."""
try:
- new = self.binfo
+ binfo = self.binfo
except AttributeError:
- # Node arrived here without build info; apparently it
- # doesn't need it, so don't bother calculating or storing
- # it.
- new = None
-
- # Reset this Node's cached state since it was just built and
- # various state has changed.
- self.clear()
+ # Apparently this node doesn't need build info, so
+ # don't bother calculating or storing it.
+ pass
+ else:
+ self.ninfo.update(self)
+ self.store_info()
- if new:
- # It had build info, so it should be stored in the signature
- # cache. However, if the build info included a content
- # signature then it must be recalculated before being stored.
- if hasattr(new.ninfo, 'csig'):
- self.get_csig()
- else:
- new.ninfo.update(self)
- self.binfo = new
- self.store_info(self.binfo)
+ #
+ #
+ #
def add_to_waiting_s_e(self, node):
self.waiting_s_e[node] = 1
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.
self.clear_memoized_values()
+ self.ninfo = self.new_ninfo()
self.executor_cleanup()
- self.del_binfo()
try:
delattr(self, '_calculated_sig')
except AttributeError:
pass
self.includes = None
self.found_includes = {}
- self.implicit = None
def clear_memoized_values(self):
self._memo = {}
- def visited(self):
- """Called just after this node has been visited
- without requiring a build.."""
- pass
-
def builder_set(self, builder):
self.builder = builder
+ try:
+ del self.executor
+ except AttributeError:
+ pass
def has_builder(self):
"""Return whether this Node has a builder or not.
except AttributeError:
# There was no explicit builder for this Node, so initialize
# the self.builder attribute to None now.
- self.builder = None
- b = self.builder
+ b = self.builder = None
return not b is None
def set_explicit(self, is_explicit):
"""
return self.has_builder() or self.side_effect
- def is_pseudo_derived(self):
- """
- Returns true iff this node is built, but should use a source path
- when duplicate=0 and should contribute a content signature (i.e.
- source signature) when used as a source for other derived files.
- """
- return 0
-
def alter_targets(self):
"""Return a list of alternate targets for this Node.
"""
if implicit_cache and not implicit_deps_changed:
implicit = self.get_stored_implicit()
if implicit is not None:
- factory = build_env.get_factory(self.builder.source_factory)
- nodes = []
- for i in implicit:
- try:
- n = factory(i)
- except TypeError:
- # The implicit dependency was cached as one type
- # of Node last time, but the configuration has
- # changed (probably) and it's a different type
- # this time. Just ignore the mismatch and go
- # with what our current configuration says the
- # Node is.
- pass
- else:
- nodes.append(n)
- self._add_child(self.implicit, self.implicit_dict, nodes)
- calc = build_env.get_calculator()
- if implicit_deps_unchanged or self.current(calc):
+ # We now add the implicit dependencies returned from the
+ # stored .sconsign entry to have already been converted
+ # to Nodes for us. (We used to run them through a
+ # source_factory function here.)
+ self._add_child(self.implicit, self.implicit_dict, implicit)
+ if implicit_deps_unchanged or self.is_up_to_date():
return
- # one of this node's sources has changed, so
- # we need to recalculate the implicit deps,
- # and the bsig:
+ # one of this node's sources has changed,
+ # so we must recalculate the implicit deps:
self.implicit = []
self.implicit_dict = {}
self._children_reset()
NodeInfo = NodeInfoBase
BuildInfo = BuildInfoBase
- def calculator(self):
- import SCons.Defaults
-
- env = self.env or SCons.Defaults.DefaultEnvironment()
- return env.get_calculator()
-
- memoizer_counters.append(SCons.Memoize.CountValue('calc_signature'))
-
- def calc_signature(self, calc=None):
- """
- Select and calculate the appropriate build signature for a node.
-
- self - the node
- calc - the signature calculation module
- returns - the signature
- """
- try:
- return self._memo['calc_signature']
- except KeyError:
- pass
- if self.is_derived():
- import SCons.Defaults
-
- env = self.env or SCons.Defaults.DefaultEnvironment()
- if env.use_build_signature():
- result = self.get_bsig(calc)
- else:
- result = self.get_csig(calc)
- elif not self.rexists():
- result = None
- else:
- result = self.get_csig(calc)
- self._memo['calc_signature'] = result
- return result
-
def new_ninfo(self):
- return self.NodeInfo(self)
-
- def new_binfo(self):
- return self.BuildInfo(self)
+ ninfo = self.NodeInfo(self)
+ return ninfo
- def get_binfo(self):
+ def get_ninfo(self):
try:
- return self.binfo
+ return self.ninfo
except AttributeError:
- self.binfo = self.new_binfo()
- return self.binfo
+ self.ninfo = self.new_ninfo()
+ return self.ninfo
- def del_binfo(self):
- """Delete the build info from this node."""
- try:
- delattr(self, 'binfo')
- except AttributeError:
- pass
+ def new_binfo(self):
+ binfo = self.BuildInfo(self)
+ return binfo
- def gen_binfo(self, calc=None, scan=1):
+ def get_binfo(self):
"""
- Generate a node's build signature, the digested signatures
- of its dependency files and build information.
+ Fetch a node's build information.
node - the node whose sources will be collected
cache - alternate node to use for the signature cache
already built and updated by someone else, if that's
what's wanted.
"""
+ try:
+ return self.binfo
+ except AttributeError:
+ pass
- if calc is None:
- calc = self.calculator()
-
- binfo = self.get_binfo()
-
- if scan:
- self.scan()
+ binfo = self.new_binfo()
+ self.binfo = binfo
executor = self.get_executor()
- def calc_signature(node, calc=calc):
- return node.calc_signature(calc)
sources = executor.get_unignored_sources(self.ignore)
- sourcesigs = executor.process_sources(calc_signature, self.ignore)
depends = self.depends
implicit = self.implicit or []
depends = filter(self.do_not_ignore, depends)
implicit = filter(self.do_not_ignore, implicit)
- dependsigs = map(calc_signature, depends)
- implicitsigs = map(calc_signature, implicit)
+ def get_ninfo(node):
+ return node.get_ninfo()
- sigs = sourcesigs + dependsigs + implicitsigs
+ sourcesigs = map(get_ninfo, sources)
+ dependsigs = map(get_ninfo, depends)
+ implicitsigs = map(get_ninfo, implicit)
if self.has_builder():
binfo.bact = str(executor)
- binfo.bactsig = calc.module.signature(executor)
- sigs.append(binfo.bactsig)
+ binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
binfo.bsources = sources
binfo.bdepends = depends
binfo.bdependsigs = dependsigs
binfo.bimplicitsigs = implicitsigs
- binfo.ninfo.bsig = calc.module.collect(filter(None, sigs))
-
return binfo
- def get_bsig(self, calc=None):
- binfo = self.get_binfo()
+ def del_binfo(self):
+ """Delete the build info from this node."""
try:
- return binfo.ninfo.bsig
+ delattr(self, 'binfo')
except AttributeError:
- self.binfo = self.gen_binfo(calc)
- return self.binfo.ninfo.bsig
+ pass
- def get_csig(self, calc=None):
- binfo = self.get_binfo()
+ def get_csig(self):
try:
- return binfo.ninfo.csig
+ return self.ninfo.csig
except AttributeError:
- if calc is None:
- calc = self.calculator()
- csig = binfo.ninfo.csig = calc.module.signature(self)
- return csig
+ ninfo = self.get_ninfo()
+ ninfo.csig = SCons.Util.MD5signature(self.get_contents())
+ return self.ninfo.csig
+
+ def get_cachedir_csig(self):
+ return self.get_csig()
- def store_info(self, obj):
+ def store_info(self):
"""Make the build signature permanent (that is, store it in the
.sconsign file or equivalent)."""
pass
+ def do_not_store_info(self):
+ pass
+
def get_stored_info(self):
return None
def missing(self):
return not self.is_derived() and \
- not self.is_pseudo_derived() and \
not self.linked and \
not self.rexists()
-
- def prepare(self):
- """Prepare for this Node to be created.
- The default implemenation checks that all children either exist
- or are derived.
- """
- l = self.depends
- if not self.implicit is None:
- l = l + self.implicit
- missing_sources = self.get_executor().get_missing_sources() \
- + filter(lambda c: c.missing(), l)
- if missing_sources:
- desc = "Source `%s' not found, needed by target `%s'." % (missing_sources[0], self)
- raise SCons.Errors.StopError, desc
def remove(self):
"""Remove this Node: no-op by default."""
def _add_child(self, collection, dict, child):
"""Adds 'child' to 'collection', first checking 'dict' to see
if it's already present."""
- if type(child) is not type([]):
- child = [child]
- for c in child:
- if not isinstance(c, Node):
- raise TypeError, c
+ #if type(child) is not type([]):
+ # child = [child]
+ #for c in child:
+ # if not isinstance(c, Node):
+ # raise TypeError, c
added = None
for c in child:
if not dict.has_key(c):
def _children_reset(self):
self.clear_memoized_values()
# We need to let the Executor clear out any calculated
- # bsig info that it's cached so we can re-calculate it.
+ # build info that it's cached so we can re-calculate it.
self.executor_cleanup()
def do_not_ignore(self, node):
def get_state(self):
return self.state
- def current(self, calc=None):
+ def state_has_changed(self, target, prev_ni):
+ return (self.state != SCons.Node.up_to_date)
+
+ def get_env(self):
+ env = self.env
+ if not env:
+ import SCons.Defaults
+ env = SCons.Defaults.DefaultEnvironment()
+ return env
+
+ def changed_since_last_build(self, target, prev_ni):
+ """
+
+ Must be overridden in a specific subclass to return True if this
+ Node (a dependency) has changed since the last time it was used
+ to build the specified target. prev_ni is this Node's state (for
+ example, its file timestamp, length, maybe content signature)
+ as of the last time the target was built.
+
+ Note that this method is called through the dependency, not the
+ target, because a dependency Node must be able to use its own
+ logic to decide if it changed. For example, File Nodes need to
+ obey if we're configured to use timestamps, but Python Value Nodes
+ never use timestamps and always use the content. If this method
+ were called through the target, then each Node's implementation
+ of this method would have to have more complicated logic to
+ handle all the different Node types on which it might depend.
+ """
+ raise NotImplementedError
+
+ def Decider(self, function):
+ SCons.Util.AddMethod(self, function, 'changed_since_last_build')
+
+ def changed(self, node=None):
+ """
+ Returns if the node is up-to-date with respect to the BuildInfo
+ stored last time it was built. The default behavior is to compare
+ it against our own previously stored BuildInfo, but the stored
+ BuildInfo from another Node (typically one in a Repository)
+ can be used instead.
+
+ Note that we now *always* check every dependency. We used to
+ short-circuit the check by returning as soon as we detected
+ any difference, but we now rely on checking every dependency
+ to make sure that any necessary Node information (for example,
+ the content signature of an #included .h file) is updated.
+ """
+ t = 0
+ if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
+ if node is None:
+ node = self
+
+ result = False
+
+ bi = node.get_stored_info().binfo
+ then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs
+ children = self.children()
+
+ diff = len(children) - len(then)
+ if diff:
+ # The old and new dependency lists are different lengths.
+ # This always indicates that the Node must be rebuilt.
+ # We also extend the old dependency list with enough None
+ # entries to equal the new dependency list, for the benefit
+ # of the loop below that updates node information.
+ then.extend([None] * diff)
+ result = True
+
+ for child, prev_ni in zip(children, then):
+ if child.changed_since_last_build(self, prev_ni):
+ if t: Trace(': %s changed' % child)
+ result = True
+
+ contents = self.get_executor().get_contents()
+ if self.has_builder():
+ import SCons.Util
+ newsig = SCons.Util.MD5signature(contents)
+ if bi.bactsig != newsig:
+ if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
+ result = True
+
+ if not result:
+ if t: Trace(': up to date')
+
+ if t: Trace('\n')
+
+ return result
+
+ def is_up_to_date(self):
"""Default check for whether the Node is current: unknown Node
subtypes are always out of date, so they will always get built."""
return None
- def children_are_up_to_date(self, calc=None):
+ def children_are_up_to_date(self):
"""Alternate check for whether the Node is current: If all of
our children were up-to-date, then this Node was up-to-date, too.
The SCons.Node.Alias and SCons.Node.Python.Value subclasses
rebind their current() method to this method."""
# Allow the children to calculate their signatures.
- self.binfo = self.gen_binfo(calc)
+ self.binfo = self.get_binfo()
if self.always_build:
return None
state = 0
old = self.get_stored_info()
if old is None:
return None
+
+ old = old.binfo
old.prepare_dependencies()
try:
for k in new_bkids:
if not k in old_bkids:
lines.append("`%s' is a new dependency\n" % stringify(k))
- elif osig[k] != nsig[k]:
+ elif k.changed_since_last_build(self, osig[k]):
lines.append("`%s' changed\n" % stringify(k))
if len(lines) == 0 and old_bkids != new_bkids:
Holds all the options, updates the environment with the variables,
and renders the help text.
"""
- def __init__(self, files=None, args={}, is_global=1):
+ def __init__(self, files=[], args={}, is_global=1):
"""
files - [optional] List of option configuration files to load
(backward compatibility) If a single string is passed it is
"""
self.options = []
self.args = args
- self.files = None
- if SCons.Util.is_String(files):
- self.files = [ files ]
- elif files:
- self.files = files
+ if not SCons.Util.is_List(files):
+ if files:
+ files = [ files ]
+ else:
+ files = []
+ self.files = files
# create the singleton instance
if is_global:
values[option.key] = option.default
# next set the value specified in the options file
- if self.files:
- for filename in self.files:
- if os.path.exists(filename):
- execfile(filename, values)
+ for filename in self.files:
+ if os.path.exists(filename):
+ execfile(filename, values)
# finally set the values specified on the command line
if args is None:
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import SCons.compat
+
import os
import re
import string
import SCons.Warnings
import SCons.Conftest
+from SCons.Debug import Trace
+
# Turn off the Conftest error logging
SCons.Conftest.LogInputFiles = 0
SCons.Conftest.LogErrorMessages = 0
"""
result = None # -> 0/None -> no error, != 0 error
string = None # the stdout / stderr output when building the target
-
- def __init__(self, node, result, string, sig):
- SCons.Node.FS.FileBuildInfo.__init__(self, node)
+
+ def set_build_result(self, result, string):
self.result = result
self.string = string
- self.ninfo.bsig = sig
class Streamer:
"""
if not isinstance(bi, SConfBuildInfo):
SCons.Warnings.warn(SConfWarning,
- "The stored build information has an unexpected class.")
+ "The stored build information has an unexpected class: %s" % bi.__class__)
else:
self.display("The original builder output was:\n" +
string.replace(" |" + str(bi.string),
# cached_error is 1, if the node(s) are up_to_date, but the
# build will fail
# cachable is 0, if some nodes are not in our cache
- is_up_to_date = 1
- cached_error = 0
- cachable = 1
+ T = 0
+ changed = False
+ cached_error = False
+ cachable = True
for t in self.targets:
- bi = t.get_stored_info()
+ if T: Trace('%s' % (t))
+ bi = t.get_stored_info().binfo
if isinstance(bi, SConfBuildInfo):
+ if T: Trace(': SConfBuildInfo')
if cache_mode == CACHE:
t.set_state(SCons.Node.up_to_date)
+ if T: Trace(': set_state(up_to-date)')
else:
- new_bsig = t.calc_signature(sconf_global.calc)
- if t.env.use_build_signature():
- old_bsig = bi.ninfo.bsig
- else:
- old_bsig = bi.ninfo.csig
- is_up_to_date = (is_up_to_date and new_bsig == old_bsig)
+ if T: Trace(': get_state() %s' % t.get_state())
+ if T: Trace(': changed() %s' % t.changed())
+ if (t.get_state() != SCons.Node.up_to_date and t.changed()):
+ changed = True
+ if T: Trace(': changed %s' % changed)
cached_error = cached_error or bi.result
else:
+ if T: Trace(': else')
# the node hasn't been built in a SConf context or doesn't
# exist
- cachable = 0
- is_up_to_date = 0
- return (is_up_to_date, cached_error, cachable)
+ cachable = False
+ changed = ( t.get_state() != SCons.Node.up_to_date )
+ if T: Trace(': changed %s' % changed)
+ if T: Trace('\n')
+ return (not changed, cached_error, cachable)
def execute(self):
+ if not self.targets[0].has_builder():
+ return
+
sconf = sconf_global
is_up_to_date, cached_error, cachable = self.collect_node_states()
if cached_error and is_up_to_date:
self.display("Building \"%s\" failed in a previous run and all "
"its sources are up to date." % str(self.targets[0]))
- self.display_cached_string(self.targets[0].get_stored_info())
+ binfo = self.targets[0].get_stored_info().binfo
+ self.display_cached_string(binfo)
raise SCons.Errors.BuildError # will be 'caught' in self.failed
elif is_up_to_date:
self.display("\"%s\" is up to date." % str(self.targets[0]))
- self.display_cached_string(self.targets[0].get_stored_info())
+ binfo = self.targets[0].get_stored_info().binfo
+ self.display_cached_string(binfo)
elif dryrun:
raise ConfigureDryRunError(self.targets[0])
else:
except SystemExit:
exc_value = sys.exc_info()[1]
raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
- except:
+ except Exception, e:
for t in self.targets:
- sig = t.calc_signature(sconf.calc)
- string = s.getvalue()
- binfo = SConfBuildInfo(t,1,string,sig)
- t.dir.sconsign().set_entry(t.name, binfo)
- raise
+ binfo = t.get_binfo()
+ binfo.__class__ = SConfBuildInfo
+ binfo.set_build_result(1, s.getvalue())
+ sconsign_entry = SCons.SConsign.SConsignEntry()
+ sconsign_entry.binfo = binfo
+ #sconsign_entry.ninfo = self.get_ninfo()
+ # We'd like to do this as follows:
+ # t.store_info(binfo)
+ # However, we need to store it as an SConfBuildInfo
+ # object, and store_info() will turn it into a
+ # regular FileNodeInfo if the target is itself a
+ # regular File.
+ sconsign = t.dir.sconsign()
+ sconsign.set_entry(t.name, sconsign_entry)
+ sconsign.merge()
+ raise e
else:
for t in self.targets:
- sig = t.calc_signature(sconf.calc)
- string = s.getvalue()
- binfo = SConfBuildInfo(t,0,string,sig)
- t.dir.sconsign().set_entry(t.name, binfo)
+ binfo = t.get_binfo()
+ binfo.__class__ = SConfBuildInfo
+ binfo.set_build_result(0, s.getvalue())
+ sconsign_entry = SCons.SConsign.SConsignEntry()
+ sconsign_entry.binfo = binfo
+ #sconsign_entry.ninfo = self.get_ninfo()
+ # We'd like to do this as follows:
+ # t.store_info(binfo)
+ # However, we need to store it as an SConfBuildInfo
+ # object, and store_info() will turn it into a
+ # regular FileNodeInfo if the target is itself a
+ # regular File.
+ sconsign = t.dir.sconsign()
+ sconsign.set_entry(t.name, sconsign_entry)
+ sconsign.merge()
class SConf:
"""This is simply a class to represent a configure context. After
self.AddTests(default_tests)
self.AddTests(custom_tests)
self.confdir = SConfFS.Dir(env.subst(conf_dir))
- self.calc = None
if not config_h is None:
config_h = SConfFS.File(config_h)
self.config_h = config_h
def Finish(self):
"""Call this method after finished with your tests:
- env = sconf.Finish()"""
+ env = sconf.Finish()
+ """
self._shutdown()
return self.env
old_os_dir = os.getcwd()
SConfFS.chdir(SConfFS.Top, change_os_dir=1)
+ # Because we take responsibility here for writing out our
+ # own .sconsign info (see SConfBuildTask.execute(), above),
+ # we override the store_info() method with a null place-holder
+ # so we really control how it gets written.
+ for n in nodes:
+ n.store_info = n.do_not_store_info
+
ret = 1
try:
import SCons.SConsign
SCons.SConsign.write() # simulate normal scons-finish
for n in sys.modules.keys():
- if string.split(n, '.')[0] == 'SCons':
+ if string.split(n, '.')[0] == 'SCons' and n[:12] != 'SCons.compat':
m = sys.modules[n]
if type(m) is ModuleType:
# if this is really a scons module, clear its namespace
def checks(self, sconf, TryFuncString):
TryFunc = self.SConf.SConf.__dict__[TryFuncString]
- res1 = TryFunc( sconf, "int main() { return 0; }", ".c" )
+ res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" )
res2 = TryFunc( sconf,
- '#include "no_std_header.h"\nint main() {return 0; }',
+ '#include "no_std_header.h"\nint main() {return 0; }\n',
'.c' )
return (res1,res2)
sconf = self.SConf.SConf(self.scons_env,
conf_dir=self.test.workpath('config.tests'),
log_file=self.test.workpath('config.log'))
- test_h = self.test.write( self.test.workpath('config.tests', 'no_std_header.h'),
- "/* we are changing a dependency now */" );
+ no_std_header_h = self.test.workpath('config.tests', 'no_std_header.h')
+ test_h = self.test.write( no_std_header_h,
+ "/* we are changing a dependency now */\n" );
try:
res = checks( self, sconf, TryFunc )
log = self.test.read( self.test.workpath('config.log') )
pass
def clear(self):
pass
- def current(self, calc=None):
+ def is_up_to_date(self):
return None
def prepare(self):
pass
pass
def get_stored_info(self):
pass
- def calc_signature(self, calc):
+ def do_not_store_info(self):
pass
def get_executor(self):
class Executor:
}
"""
res1 = sconf.TryRun( prog, ".c" )
- res2 = sconf.TryRun( "not a c program", ".c" )
+ res2 = sconf.TryRun( "not a c program\n", ".c" )
return (res1, res2)
self._resetSConfState()
"""Test SConf.TryAction
"""
def actionOK(target, source, env):
- open(str(target[0]), "w").write( "RUN OK" )
+ open(str(target[0]), "w").write( "RUN OK\n" )
return None
def actionFAIL(target, source, env):
return 1
log_file=self.test.workpath('config.log'))
try:
(ret, output) = sconf.TryAction(action=actionOK)
- assert ret and output == "RUN OK", (ret, output)
+ assert ret and output == "RUN OK" + os.linesep, (ret, output)
(ret, output) = sconf.TryAction(action=actionFAIL)
assert not ret and output == "", (ret, output)
finally:
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import SCons.compat
+
import cPickle
import os
import os.path
import SCons.dblite
-import SCons.Sig
import SCons.Warnings
def corrupt_dblite_warning(filename):
else:
syncmethod()
+class SConsignEntry:
+ """
+ Wrapper class for the generic entry in a .sconsign file.
+ The Node subclass populates it with attributes as it pleases.
+
+ XXX As coded below, we do expect a '.binfo' attribute to be added,
+ but we'll probably generalize this in the next refactorings.
+ """
+ current_version_id = 1
+ def __init__(self):
+ # Create an object attribute from the class attribute so it ends up
+ # in the pickled data in the .sconsign file.
+ _version_id = self.current_version_id
+ def convert_to_sconsign(self):
+ self.binfo.convert_to_sconsign()
+ def convert_from_sconsign(self, dir, name):
+ self.binfo.convert_from_sconsign(dir, name)
+
class Base:
"""
This is the controlling class for the signatures for the collection of
methods for fetching and storing the individual bits of information
that make up signature entry.
"""
- def __init__(self, module=None):
- """
- module - the signature module being used
- """
-
- self.module = module or SCons.Sig.default_calc.module
+ def __init__(self):
self.entries = {}
- self.dirty = 0
+ self.dirty = False
+ self.to_be_merged = {}
def get_entry(self, filename):
"""
Set the entry.
"""
self.entries[filename] = obj
- self.dirty = 1
+ self.dirty = True
def do_not_set_entry(self, filename, obj):
pass
+ def store_info(self, filename, node):
+ entry = node.get_stored_info()
+ entry.binfo.merge(node.get_binfo())
+ self.to_be_merged[filename] = node
+ self.dirty = True
+
+ def do_not_store_info(self, filename, node):
+ pass
+
+ def merge(self):
+ for key, node in self.to_be_merged.items():
+ entry = node.get_stored_info()
+ try:
+ ninfo = entry.ninfo
+ except AttributeError:
+ # This happens with SConf Nodes, because the configuration
+ # subsystem takes direct control over how the build decision
+ # is made and its information stored.
+ pass
+ else:
+ ninfo.merge(node.get_ninfo())
+ self.entries[key] = entry
+ self.to_be_merged = {}
+
class DB(Base):
"""
A Base subclass that reads and writes signature information
from a global .sconsign.db* file--the actual file suffix is
- determined by the specified database module.
+ determined by the database module.
"""
- def __init__(self, dir, module=None):
- Base.__init__(self, module)
+ def __init__(self, dir):
+ Base.__init__(self)
self.dir = dir
# a file there. Don't actually set any entry info, so we
# won't try to write to that .sconsign.dblite file.
self.set_entry = self.do_not_set_entry
+ self.store_info = self.do_not_store_info
global sig_files
sig_files.append(self)
if not self.dirty:
return
+ self.merge()
+
db, mode = Get_DataBase(self.dir)
# Write using the path relative to the top of the SConstruct
syncmethod()
class Dir(Base):
- def __init__(self, fp=None, module=None):
+ def __init__(self, fp=None, dir=None):
"""
fp - file pointer to read entries from
- module - the signature module being used
"""
- Base.__init__(self, module)
+ Base.__init__(self)
+
+ if not fp:
+ return
- if fp:
- self.entries = cPickle.load(fp)
- if type(self.entries) is not type({}):
- self.entries = {}
- raise TypeError
+ self.entries = cPickle.load(fp)
+ if type(self.entries) is not type({}):
+ self.entries = {}
+ raise TypeError
+
+ if dir:
+ for key, entry in self.entries.items():
+ entry.convert_from_sconsign(dir, key)
class DirFile(Dir):
"""
Encapsulates reading and writing a per-directory .sconsign file.
"""
- def __init__(self, dir, module=None):
+ def __init__(self, dir):
"""
dir - the directory for the file
- module - the signature module being used
"""
self.dir = dir
fp = None
try:
- Dir.__init__(self, fp, module)
+ Dir.__init__(self, fp, dir)
except KeyboardInterrupt:
raise
except:
global sig_files
sig_files.append(self)
- def get_entry(self, filename):
- """
- Fetch the specified entry attribute, converting from .sconsign
- format to in-memory format.
- """
- entry = Dir.get_entry(self, filename)
- entry.convert_from_sconsign(self.dir, filename)
- return entry
-
def write(self, sync=1):
"""
Write the .sconsign file to disk.
to the .sconsign file. Either way, always try to remove
the temporary file at the end.
"""
- if self.dirty:
- temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
+ if not self.dirty:
+ return
+
+ self.merge()
+
+ temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
+ try:
+ file = open(temp, 'wb')
+ fname = temp
+ except IOError:
try:
- file = open(temp, 'wb')
- fname = temp
+ file = open(self.sconsign, 'wb')
+ fname = self.sconsign
except IOError:
- try:
- file = open(self.sconsign, 'wb')
- fname = self.sconsign
- except IOError:
- return
- for key, entry in self.entries.items():
- entry.convert_to_sconsign()
- cPickle.dump(self.entries, file, 1)
- file.close()
- if fname != self.sconsign:
- try:
- mode = os.stat(self.sconsign)[0]
- os.chmod(self.sconsign, 0666)
- os.unlink(self.sconsign)
- except (IOError, OSError):
- # Try to carry on in the face of either OSError
- # (things like permission issues) or IOError (disk
- # or network issues). If there's a really dangerous
- # issue, it should get re-raised by the calls below.
- pass
- try:
- os.rename(fname, self.sconsign)
- except OSError:
- # An OSError failure to rename may indicate something
- # like the directory has no write permission, but
- # the .sconsign file itself might still be writable,
- # so try writing on top of it directly. An IOError
- # here, or in any of the following calls, would get
- # raised, indicating something like a potentially
- # serious disk or network issue.
- open(self.sconsign, 'wb').write(open(fname, 'rb').read())
- os.chmod(self.sconsign, mode)
+ return
+ for key, entry in self.entries.items():
+ entry.convert_to_sconsign()
+ cPickle.dump(self.entries, file, 1)
+ file.close()
+ if fname != self.sconsign:
try:
- os.unlink(temp)
+ mode = os.stat(self.sconsign)[0]
+ os.chmod(self.sconsign, 0666)
+ os.unlink(self.sconsign)
except (IOError, OSError):
+ # Try to carry on in the face of either OSError
+ # (things like permission issues) or IOError (disk
+ # or network issues). If there's a really dangerous
+ # issue, it should get re-raised by the calls below.
pass
+ try:
+ os.rename(fname, self.sconsign)
+ except OSError:
+ # An OSError failure to rename may indicate something
+ # like the directory has no write permission, but
+ # the .sconsign file itself might still be writable,
+ # so try writing on top of it directly. An IOError
+ # here, or in any of the following calls, would get
+ # raised, indicating something like a potentially
+ # serious disk or network issue.
+ open(self.sconsign, 'wb').write(open(fname, 'rb').read())
+ os.chmod(self.sconsign, mode)
+ try:
+ os.unlink(temp)
+ except (IOError, OSError):
+ pass
ForDirectory = DB
import SCons.SConsign
class BuildInfo:
+ def merge(self, object):
+ pass
+
+class DummySConsignEntry:
def __init__(self, name):
self.name = name
+ self.binfo = BuildInfo()
def convert_to_sconsign(self):
self.c_to_s = 1
def convert_from_sconsign(self, dir, name):
self.c_from_s = 1
-class DummyModule:
- def to_string(self, sig):
- return str(sig)
-
- def from_string(self, sig):
- return int(sig)
-
class FS:
def __init__(self, top):
self.Top = top
self.Top.repositories = []
class DummyNode:
- def __init__(self, path='not_a_valid_path'):
+ def __init__(self, path='not_a_valid_path', binfo=None):
self.path = path
self.tpath = path
self.fs = FS(self)
+ self.binfo = binfo
+ def get_stored_info(self):
+ return self.binfo
+ def get_binfo(self):
+ return self.binfo
class SConsignTestCase(unittest.TestCase):
def setUp(self):
class BaseTestCase(SConsignTestCase):
- def runTest(self):
- aaa = BuildInfo('aaa')
- bbb = BuildInfo('bbb')
+ def test_Base(self):
+ aaa = DummySConsignEntry('aaa')
+ bbb = DummySConsignEntry('bbb')
bbb.arg1 = 'bbb arg1'
- ccc = BuildInfo('ccc')
+ ccc = DummySConsignEntry('ccc')
ccc.arg2 = 'ccc arg2'
f = SCons.SConsign.Base()
f.set_entry('aaa', aaa)
f.set_entry('bbb', bbb)
+ #f.merge()
+
e = f.get_entry('aaa')
assert e == aaa, e
assert e.name == 'aaa', e.name
assert not hasattr(e, 'arg2'), e
f.set_entry('bbb', ccc)
+
e = f.get_entry('bbb')
assert e.name == 'ccc', e.name
assert not hasattr(e, 'arg1'), e
assert e.arg2 == 'ccc arg2', e.arg1
- ddd = BuildInfo('ddd')
- eee = BuildInfo('eee')
- fff = BuildInfo('fff')
+ ddd = DummySConsignEntry('ddd')
+ eee = DummySConsignEntry('eee')
+ fff = DummySConsignEntry('fff')
fff.arg = 'fff arg'
- f = SCons.SConsign.Base(DummyModule())
+ f = SCons.SConsign.Base()
f.set_entry('ddd', ddd)
f.set_entry('eee', eee)
assert not hasattr(e, 'arg'), e
f.set_entry('eee', fff)
+
+ e = f.get_entry('eee')
+ assert e.name == 'fff', e.name
+ assert e.arg == 'fff arg', e.arg
+
+ def test_store_info(self):
+ aaa = DummySConsignEntry('aaa')
+ bbb = DummySConsignEntry('bbb')
+ bbb.arg1 = 'bbb arg1'
+ ccc = DummySConsignEntry('ccc')
+ ccc.arg2 = 'ccc arg2'
+
+ f = SCons.SConsign.Base()
+ f.store_info('aaa', DummyNode('aaa', aaa))
+ f.store_info('bbb', DummyNode('bbb', bbb))
+
+ try:
+ e = f.get_entry('aaa')
+ except KeyError:
+ pass
+ else:
+ raise "unexpected entry %s" % e
+
+ try:
+ e = f.get_entry('bbb')
+ except KeyError:
+ pass
+ else:
+ raise "unexpected entry %s" % e
+
+ f.merge()
+
+ e = f.get_entry('aaa')
+ assert e == aaa, "aaa = %s, e = %s" % (aaa, e)
+ assert e.name == 'aaa', e.name
+
+ e = f.get_entry('bbb')
+ assert e == bbb, "bbb = %s, e = %s" % (bbb, e)
+ assert e.name == 'bbb', e.name
+ assert e.arg1 == 'bbb arg1', e.arg1
+ assert not hasattr(e, 'arg2'), e
+
+ f.store_info('bbb', DummyNode('bbb', ccc))
+
+ e = f.get_entry('bbb')
+ assert e == bbb, e
+ assert e.name == 'bbb', e.name
+ assert e.arg1 == 'bbb arg1', e.arg1
+ assert not hasattr(e, 'arg2'), e
+
+ f.merge()
+
+ e = f.get_entry('bbb')
+ assert e.name == 'ccc', e.name
+ assert not hasattr(e, 'arg1'), e
+ assert e.arg2 == 'ccc arg2', e.arg1
+
+ ddd = DummySConsignEntry('ddd')
+ eee = DummySConsignEntry('eee')
+ fff = DummySConsignEntry('fff')
+ fff.arg = 'fff arg'
+
+ f = SCons.SConsign.Base()
+ f.store_info('ddd', DummyNode('ddd', ddd))
+ f.store_info('eee', DummyNode('eee', eee))
+
+ f.merge()
+
+ e = f.get_entry('ddd')
+ assert e == ddd, e
+ assert e.name == 'ddd', e.name
+
+ e = f.get_entry('eee')
+ assert e == eee, e
+ assert e.name == 'eee', e.name
+ assert not hasattr(e, 'arg'), e
+
+ f.store_info('eee', DummyNode('eee', fff))
+
+ e = f.get_entry('eee')
+ assert e == eee, e
+ assert e.name == 'eee', e.name
+ assert not hasattr(e, 'arg'), e
+
+ f.merge()
+
e = f.get_entry('eee')
assert e.name == 'fff', e.name
assert e.arg == 'fff arg', e.arg
class SConsignDBTestCase(SConsignTestCase):
- def runTest(self):
+ def test_SConsignDB(self):
save_DataBase = SCons.SConsign.DataBase
SCons.SConsign.DataBase = {}
try:
d1 = SCons.SConsign.DB(DummyNode('dir1'))
- d1.set_entry('aaa', BuildInfo('aaa name'))
- d1.set_entry('bbb', BuildInfo('bbb name'))
+ d1.set_entry('aaa', DummySConsignEntry('aaa name'))
+ d1.set_entry('bbb', DummySConsignEntry('bbb name'))
+
aaa = d1.get_entry('aaa')
assert aaa.name == 'aaa name'
bbb = d1.get_entry('bbb')
assert bbb.name == 'bbb name'
d2 = SCons.SConsign.DB(DummyNode('dir2'))
- d2.set_entry('ccc', BuildInfo('ccc name'))
- d2.set_entry('ddd', BuildInfo('ddd name'))
+ d2.set_entry('ccc', DummySConsignEntry('ccc name'))
+ d2.set_entry('ddd', DummySConsignEntry('ddd name'))
+
ccc = d2.get_entry('ccc')
assert ccc.name == 'ccc name'
ddd = d2.get_entry('ddd')
assert ddd.name == 'ddd name'
d31 = SCons.SConsign.DB(DummyNode('dir3/sub1'))
- d31.set_entry('eee', BuildInfo('eee name'))
- d31.set_entry('fff', BuildInfo('fff name'))
+ d31.set_entry('eee', DummySConsignEntry('eee name'))
+ d31.set_entry('fff', DummySConsignEntry('fff name'))
+
eee = d31.get_entry('eee')
assert eee.name == 'eee name'
fff = d31.get_entry('fff')
assert fff.name == 'fff name'
d32 = SCons.SConsign.DB(DummyNode('dir3%ssub2' % os.sep))
- d32.set_entry('ggg', BuildInfo('ggg name'))
- d32.set_entry('hhh', BuildInfo('hhh name'))
+ d32.set_entry('ggg', DummySConsignEntry('ggg name'))
+ d32.set_entry('hhh', DummySConsignEntry('hhh name'))
+
ggg = d32.get_entry('ggg')
assert ggg.name == 'ggg name'
hhh = d32.get_entry('hhh')
assert hhh.name == 'hhh name'
+
finally:
+
SCons.SConsign.DataBase = save_DataBase
class SConsignDirFileTestCase(SConsignTestCase):
- def runTest(self):
- bi_foo = BuildInfo('foo')
- bi_bar = BuildInfo('bar')
+ def test_SConsignDirFile(self):
+ bi_foo = DummySConsignEntry('foo')
+ bi_bar = DummySConsignEntry('bar')
- f = SCons.SConsign.DirFile(DummyNode(), DummyModule())
+ f = SCons.SConsign.DirFile(DummyNode())
f.set_entry('foo', bi_foo)
f.set_entry('bar', bi_bar)
assert e == bi_foo, e
assert e.name == 'foo', e.name
- assert bi_foo.c_from_s, bi_foo.c_from_s
-
e = f.get_entry('bar')
assert e == bi_bar, e
assert e.name == 'bar', e.name
assert not hasattr(e, 'arg'), e
- assert bi_bar.c_from_s, bi_bar.c_from_s
-
- bbb = BuildInfo('bbb')
+ bbb = DummySConsignEntry('bbb')
bbb.arg = 'bbb arg'
+
f.set_entry('bar', bbb)
+
e = f.get_entry('bar')
assert e.name == 'bbb', e.name
assert e.arg == 'bbb arg', e.arg
class SConsignFileTestCase(SConsignTestCase):
- def runTest(self):
+ def test_SConsignFile(self):
test = self.test
file = test.workpath('sconsign_file')
class writeTestCase(SConsignTestCase):
- def runTest(self):
+ def test_write(self):
test = self.test
file = test.workpath('sconsign_file')
SCons.SConsign.DataBase = {}
SCons.SConsign.File(file, fake_dbm)
- f = SCons.SConsign.DB(DummyNode(), DummyModule())
+ f = SCons.SConsign.DB(DummyNode())
- bi_foo = BuildInfo('foo')
- bi_bar = BuildInfo('bar')
+ bi_foo = DummySConsignEntry('foo')
+ bi_bar = DummySConsignEntry('bar')
f.set_entry('foo', bi_foo)
f.set_entry('bar', bi_bar)
assert fake_dbm.sync_count == 1, fake_dbm.sync_count
-def suite():
- suite = unittest.TestSuite()
- suite.addTest(BaseTestCase())
- suite.addTest(SConsignDBTestCase())
- suite.addTest(SConsignDirFileTestCase())
- suite.addTest(SConsignFileTestCase())
- suite.addTest(writeTestCase())
- return suite
if __name__ == "__main__":
- runner = unittest.TextTestRunner()
- result = runner.run(suite())
- if not result.wasSuccessful():
+ suite = unittest.TestSuite()
+ tclasses = [
+ BaseTestCase,
+ SConsignDBTestCase,
+ SConsignDirFileTestCase,
+ SConsignFileTestCase,
+ writeTestCase,
+ ]
+ for tclass in tclasses:
+ names = unittest.getTestCaseNames(tclass, 'test_')
+ suite.addTests(map(tclass, names))
+ if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
-
import sys
import unittest
import UserDict
-import SCons.Sig
import SCons.Scanner
if type(path) != type([]):
path = [path]
return map(self.subst, path)
- def get_calculator(self):
- return SCons.Sig.default_calc
def get_factory(self, factory):
return factory or self.fs.File
class MyNode:
def __init__(self):
self.called_has_builder = None
- self.called_current = None
+ self.called_is_up_to_date = None
self.func_called = None
def rexists(self):
return 1
def has_builder(self):
self.called_has_builder = 1
return 1
- def current(self, sig):
- self.called_current = 1
+ def is_up_to_date(self):
+ self.called_is_up_to_date = 1
return None
class IsCurrent(MyNode):
def has_builder(self):
self.called_has_builder = 1
return 1
- def current(self, sig):
- self.called_current = 1
+ def is_up_to_date(self):
+ self.called_is_up_to_date = 1
return 1
def func(node, env, path):
node.func_called = 1
hnb = HasNoBuilder()
s(hnb, env, path)
self.failUnless(hnb.called_has_builder, "did not call has_builder()")
- self.failUnless(not hnb.called_current, "did call current()")
+ self.failUnless(not hnb.called_is_up_to_date, "did call is_up_to_date()")
self.failUnless(hnb.func_called, "did not call func()")
inc = IsNotCurrent()
s(inc, env, path)
self.failUnless(inc.called_has_builder, "did not call has_builder()")
- self.failUnless(inc.called_current, "did not call current()")
+ self.failUnless(inc.called_is_up_to_date, "did not call is_up_to_date()")
self.failUnless(not inc.func_called, "did call func()")
ic = IsCurrent()
s(ic, env, path)
self.failUnless(ic.called_has_builder, "did not call has_builder()")
- self.failUnless(ic.called_current, "did not call current()")
+ self.failUnless(ic.called_is_up_to_date, "did not call is_up_to_date()")
self.failUnless(ic.func_called, "did not call func()")
class ClassicTestCase(unittest.TestCase):
import string
import SCons.Node.FS
-import SCons.Sig
import SCons.Util
def __init__(self, *args, **kw):
def current_check(node, env):
- c = not node.has_builder() or node.current(env.get_calculator())
- return c
+ return not node.has_builder() or node.is_up_to_date()
kw['scan_check'] = current_check
apply(Base.__init__, (self,) + args, kw)
import SCons.Node.FS
import SCons.SConf
import SCons.Script
-import SCons.Sig
import SCons.Taskmaster
import SCons.Util
import SCons.Warnings
first_command_start = None
last_command_end = None
+class Progressor:
+ prev = ''
+ count = 0
+ target_string = '$TARGET'
+
+ def __init__(self, obj, interval=1, file=None, overwrite=False):
+ if file is None:
+ file = sys.stdout
+
+ self.obj = obj
+ self.file = file
+ self.interval = interval
+ self.overwrite = overwrite
+
+ if callable(obj):
+ self.func = obj
+ elif SCons.Util.is_List(obj):
+ self.func = self.spinner
+ elif string.find(obj, self.target_string) != -1:
+ self.func = self.replace_string
+ else:
+ self.func = self.string
+
+ def write(self, s):
+ self.file.write(s)
+ self.file.flush()
+ self.prev = s
+
+ def erase_previous(self):
+ if self.prev:
+ length = len(self.prev)
+ if self.prev[-1] in ('\n', '\r'):
+ length = length - 1
+ self.write(' ' * length + '\r')
+ self.prev = ''
+
+ def spinner(self, node):
+ self.write(self.obj[self.count % len(self.obj)])
+
+ def string(self, node):
+ self.write(self.obj)
+
+ def replace_string(self, node):
+ self.write(string.replace(self.obj, self.target_string, str(node)))
+
+ def __call__(self, node):
+ self.count = self.count + 1
+ if (self.count % self.interval) == 0:
+ if self.overwrite:
+ self.erase_previous()
+ self.func(node)
+
+ProgressObject = SCons.Util.Null()
+
+def Progress(*args, **kw):
+ global ProgressObject
+ ProgressObject = apply(Progressor, args, kw)
+
# Task control.
#
class BuildTask(SCons.Taskmaster.Task):
"""An SCons build task."""
+ progress = ProgressObject
+
def display(self, message):
display('scons: ' + message)
+ def prepare(self):
+ self.progress(self.targets[0])
+ return SCons.Taskmaster.Task.prepare(self)
+
def execute(self):
for target in self.targets:
if target.get_state() == SCons.Node.up_to_date:
execute = remove
+ # We want the Taskmaster to update the Node states (and therefore
+ # handle reference counts, etc.), but we don't want to call
+ # back to the Node's post-build methods, which would do things
+ # we don't want, like store .sconsign information.
+ executed = SCons.Taskmaster.Task.executed_without_callbacks
+
# Have the taskmaster arrange to "execute" all of the targets, because
# we'll figure out ourselves (in remove() or show() above) whether
# anything really needs to be done.
pass
def execute(self):
- if self.targets[0].get_state() != SCons.Node.up_to_date:
+ if self.targets[0].get_state() != SCons.Node.up_to_date or \
+ (self.top and not self.targets[0].exists()):
global exit_status
exit_status = 1
self.tm.stop()
CleanTask.execute = CleanTask.show
if options.question:
SCons.SConf.dryrun = 1
+ if options.clean or options.help:
+ # If they're cleaning targets or have asked for help, replace
+ # the whole SCons.SConf module with a Null object so that the
+ # Configure() calls when reading the SConscript files don't
+ # actually do anything.
+ SCons.SConf.SConf = SCons.Util.Null
SCons.SConf.SetCacheMode(options.config)
SCons.SConf.SetProgressDisplay(progress_display)
memory_stats.append('after reading SConscript files:')
count_stats.append(('post-', 'read'))
- SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
+ if not options.help:
+ SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment())
# Now re-parse the command-line options (any to the left of a '--'
# argument, that is) with any user-defined command-line options that
except AttributeError:
pass
+ task_class.progress = ProgressObject
+
if options.random:
def order(dependencies):
"""Randomize the dependencies."""
#CommandLineTargets = []
#DefaultTargets = []
+class SConscriptReturn(Exception):
+ pass
+
launch_dir = os.path.abspath(os.curdir)
GlobalDict = None
# make sure the sconscript attr is a Node.
if isinstance(sconscript, SCons.Node.Node):
self.sconscript = sconscript
+ elif sconscript == '-':
+ self.sconscript = None
else:
self.sconscript = fs.File(str(sconscript))
# For documentation on the methods in this file, see the scons man-page
-def Return(*vars):
+def Return(*vars, **kw):
retval = []
try:
for var in vars:
else:
call_stack[-1].retval = tuple(retval)
+ stop = kw.get('stop', True)
+
+ if stop:
+ raise SConscriptReturn
+
stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
except KeyError:
pass
try:
- exec _file_ in call_stack[-1].globals
+ try:
+ exec _file_ in call_stack[-1].globals
+ except SConscriptReturn:
+ pass
finally:
if old_file is not None:
call_stack[-1].globals.update({__file__:old_file})
AddOption = Main.AddOption
GetOption = Main.GetOption
SetOption = Main.SetOption
+Progress = Main.Progress
#keep_going_on_error = Main.keep_going_on_error
#print_dtree = Main.print_dtree
sconscript_reading = 0
#
-def Options(files=None, args=ARGUMENTS):
+def Options(files=[], args=ARGUMENTS):
return SCons.Options.Options(files, args)
# The list of global functions to add to the SConscript name space
'CacheDir',
'Clean',
#The Command() method is handled separately, below.
+ 'Decider',
'Depends',
'Dir',
'NoClean',
--- /dev/null
+#
+# __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__ = """Place-holder for the old SCons.Sig module hierarchy
+
+This is no longer used, but code out there (such as the NSIS module on
+the SCons wiki) may try to import SCons.Sig. If so, we generate a warning
+that points them to the line that caused the import, and don't die.
+
+If someone actually tried to use the sub-modules or functions within
+the package (for example, SCons.Sig.MD5.signature()), then they'll still
+get an AttributeError, but at least they'll know where to start looking.
+"""
+
+import SCons.Util
+import SCons.Warnings
+
+msg = 'The SCons.Sig module no longer exists.\n' \
+ ' Remove the following "import SCons.Sig" line to eliminate this warning:'
+
+SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, msg)
+
+default_calc = None
+default_module = None
+
+class MD5Null(SCons.Util.Null):
+ def __repr__(self):
+ return "MD5Null()"
+
+class TimeStampNull(SCons.Util.Null):
+ def __repr__(self):
+ return "TimeStampNull()"
+
+MD5 = MD5Null()
+TimeStamp = TimeStampNull()
+++ /dev/null
-*,D
-*.pyc
-.*.swp
-.consign
-.sconsign
+++ /dev/null
-"""SCons.Sig.MD5
-
-The MD5 signature package for the SCons software construction
-utility.
-
-"""
-
-#
-# __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 imp
-import string
-
-# Force Python to load the builtin "md5" module. If we do this with a
-# normal import statement, then case-insensitive systems (Windows) get
-# confused and thinks there's a case mismatch with *this* MD5.py module.
-file, name, desc = imp.find_module('md5')
-try:
- md5 = imp.load_module('md5', file, name, desc)
-finally:
- if file:
- file.close()
-
-def current(new, old):
- """Return whether a new signature is up-to-date with
- respect to an old signature.
- """
- return new == old
-
-try:
- md5.new('').hexdigest
-except AttributeError:
- # The md5 objects created by the module have no native hexdigest()
- # method (*cough* 1.5.2 *cough*) so provide an equivalent.
- class new_md5:
- def __init__(self, s):
- self.m = md5.new(str(s))
- #def copy(self):
- # return self.m.copy()
- def digest(self):
- return self.m.digest()
- def hexdigest(self):
- h = string.hexdigits
- r = ''
- for c in self.m.digest():
- i = ord(c)
- r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
- return r
- def update(self, s):
- return self.m.update(s)
-
-else:
- new_md5 = lambda s: md5.new(str(s))
-
-def collect(signatures):
- """
- Collect a list of signatures into an aggregate signature.
-
- signatures - a list of signatures
- returns - the aggregate signature
- """
- if len(signatures) == 1:
- return signatures[0]
- else:
- return new_md5(string.join(signatures, ', ')).hexdigest()
-
-def signature(obj):
- """Generate a signature for an object
- """
- try:
- gc = obj.get_contents
- except AttributeError:
- raise AttributeError, "unable to fetch contents of '%s'" % str(obj)
- return new_md5(gc()).hexdigest()
-
-def to_string(signature):
- """Convert a signature to a string"""
- return signature
-
-def from_string(string):
- """Convert a string to a signature"""
- return string
+++ /dev/null
-#
-# __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 sys
-import unittest
-import string
-
-from SCons.Sig.MD5 import current, collect, signature, to_string, from_string
-
-
-
-
-class my_obj:
- """A dummy object class that satisfies the interface
- requirements of the MD5 class.
- """
-
- def __init__(self, value = ""):
- self.value = value
-
- def get_signature(self):
- if not hasattr(self, "sig"):
- self.sig = signature(self)
- return self.sig
-
- def get_contents(self):
- return self.value
-
-
-
-class MD5TestCase(unittest.TestCase):
-
- def test_current(self):
- """Test deciding if an object is up-to-date
-
- Simple comparison of different "signature" values.
- """
- obj = my_obj('111')
- assert not current(obj.get_signature(), signature(my_obj('110')))
- assert current(obj.get_signature(), signature(my_obj('111')))
- assert not current(obj.get_signature(), signature(my_obj('112')))
-
- def test_collect(self):
- """Test collecting a list of signatures into a new signature value
- """
- s = map(signature, map(my_obj, ('111', '222', '333')))
-
- assert '698d51a19d8a121ce581499d7b701668' == collect(s[0:1])
- assert '8980c988edc2c78cc43ccb718c06efd5' == collect(s[0:2])
- assert '53fd88c84ff8a285eb6e0a687e55b8c7' == collect(s)
-
- def test_signature(self):
- """Test generating a signature"""
- o1 = my_obj(value = '111')
- s = signature(o1)
- assert '698d51a19d8a121ce581499d7b701668' == s, s
-
- o2 = my_obj(value = 222)
- s = signature(o2)
- assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s
-
- try:
- signature('string')
- except AttributeError, e:
- assert string.find(str(e), "unable to fetch contents") == 0, str(e)
- else:
- raise AttributeError, "unexpected get_contents() attribute"
-
- # Make sure we don't eat AttributeErrors raised internally
- # by the get_contents() method (or anything it calls).
- caught = None
- try:
- class xxx:
- def get_contents(self):
- raise AttributeError, "internal AttributeError"
- signature(xxx())
- except AttributeError, e:
- assert str(e) == "internal AttributeError", e
- caught = 1
- assert caught, "did not catch expected AttributeError"
-
- def test_to_string(self):
- assert '698d51a19d8a121ce581499d7b701668' == to_string('698d51a19d8a121ce581499d7b701668')
-
- def test_from_string(self):
- assert '698d51a19d8a121ce581499d7b701668' == from_string('698d51a19d8a121ce581499d7b701668')
-
-if __name__ == "__main__":
- suite = unittest.makeSuite(MD5TestCase, 'test_')
- if not unittest.TextTestRunner().run(suite).wasSuccessful():
- sys.exit(1)
+++ /dev/null
-#
-# __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 sys
-import unittest
-
-from SCons.Sig.TimeStamp import current, collect, signature, to_string, from_string
-
-
-
-class my_obj:
- """A dummy object class that satisfies the interface
- requirements of the TimeStamp class.
- """
-
- def __init__(self, value = 0):
- self.value = value
-
- def get_signature(self):
- return self.value
-
- def get_timestamp(self):
- return self.value
-
-
-class TimeStampTestCase(unittest.TestCase):
-
- def test_current(self):
- """Test deciding if an object is up-to-date
-
- Simple comparison of different timestamp values.
- """
- o1 = my_obj(value = 111)
- assert not current(o1.get_signature(), 110)
- assert current(o1.get_signature(), 111)
- assert current(o1.get_signature(), 112)
-
- def test_collect(self):
- """Test collecting a list of signatures into a new signature value
- into a new timestamp value.
- """
-
- assert 111 == collect((111,))
- assert 222 == collect((111, 222))
- assert 333 == collect((333, 222, 111))
-
- def test_signature(self):
- """Test generating a signature"""
- o1 = my_obj(value = 111)
- assert 111 == signature(o1)
-
- def test_to_string(self):
- assert '111' == to_string(111)
-
- def test_from_string(self):
- assert 111 == from_string('111')
-
-
-if __name__ == "__main__":
- suite = unittest.makeSuite(TimeStampTestCase, 'test_')
- if not unittest.TextTestRunner().run(suite).wasSuccessful():
- sys.exit(1)
raise SCons.Errors.TaskmasterException(self.targets[0],
sys.exc_info())
- def executed(self):
+ def executed_without_callbacks(self):
"""
- Called when the task has been successfully executed.
+ Called when the task has been successfully executed
+ and the Taskmaster instance doesn't want to call
+ the Node's callback methods.
+ """
+ for t in self.targets:
+ if t.get_state() == SCons.Node.executing:
+ for side_effect in t.side_effects:
+ side_effect.set_state(SCons.Node.no_state)
+ t.set_state(SCons.Node.executed)
+
+ def executed_with_callbacks(self):
+ """
+ Called when the task has been successfully executed and
+ the Taskmaster instance wants to call the Node's callback
+ methods.
This may have been a do-nothing operation (to preserve build
- order), so we have to check the node's state before deciding
- whether it was "built" or just "visited."
+ order), so we must check the node's state before deciding whether
+ it was "built", in which case we call the appropriate Node method.
+ In any event, we always call "visited()", which will handle any
+ post-visit actions that must take place regardless of whether
+ or not the target was an actual built target or a source Node.
"""
for t in self.targets:
if t.get_state() == SCons.Node.executing:
+ for side_effect in t.side_effects:
+ side_effect.set_state(SCons.Node.no_state)
t.set_state(SCons.Node.executed)
t.built()
- else:
- t.visited()
+ t.visited()
+
+ executed = executed_with_callbacks
def failed(self):
"""
"""
self.out_of_date = []
for t in self.targets:
- t.disambiguate()
try:
- is_up_to_date = not t.always_build and t.current()
+ t.disambiguate().make_ready()
+ is_up_to_date = not t.has_builder() or \
+ (not t.always_build and t.is_up_to_date())
except EnvironmentError, e:
raise SCons.Errors.BuildError(node=t, errstr=e.strerror, filename=e.filename)
if is_up_to_date:
# back on the candidates list if the Node is also a waiting
# parent.
+ targets = set(self.targets)
+
parents = {}
- for t in self.targets:
+ for t in targets:
for p in t.waiting_parents.keys():
parents[p] = parents.get(p, 0) + 1
- for t in self.targets:
+ for t in targets:
for s in t.side_effects:
if s.get_state() == SCons.Node.executing:
s.set_state(SCons.Node.no_state)
if p.ref_count == 0:
self.tm.candidates.append(p)
- for t in self.targets:
+ for t in targets:
t.postprocess()
# Exception handling subsystem.
"""
def __init__(self, targets=[], tasker=Task, order=None, trace=None):
- self.top_targets = targets[:]
- self.top_targets.reverse()
+ self.original_top = targets
+ self.top_targets_left = targets[:]
+ self.top_targets_left.reverse()
self.candidates = []
self.tasker = tasker
if not order:
except IndexError:
pass
try:
- node = self.top_targets.pop()
+ node = self.top_targets_left.pop()
except IndexError:
return None
self.current_top = node
c.sort()
T.write(' children:\n %s\n ' % c)
- childinfo = map(lambda N: (N.get_state(),
- N.is_derived() or N.is_pseudo_derived(),
- N), children)
+ 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[0] == SCons.Node.failed,
- childinfo)
+ 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
continue
# Detect dependency cycles:
- pending_nodes = filter(lambda I: I[0] == SCons.Node.pending, childinfo)
+ pending_nodes = filter(lambda I: I[1] == SCons.Node.pending, childstate)
if pending_nodes:
for p in pending_nodes:
- cycle = find_cycle([p[2], node])
+ cycle = find_cycle([p[0], node])
if cycle:
desc = "Dependency cycle: " + string.join(map(str, cycle), " -> ")
if T: T.write(' dependency cycle\n')
raise SCons.Errors.UserError, desc
- # Select all of the dependencies that are derived targets
- # (that is, children who have builders or are side effects).
- derived_children = filter(lambda I: I[1], childinfo)
-
- not_started = filter(lambda I: not I[0], derived_children)
- if not_started:
- not_started = map(lambda I: I[2], not_started)
-
- # We're waiting on one more derived targets that have
- # not yet started building. Add this node to the
- # waiting_parents lists of those derived files so that
- # when they've finished building, our implicit dependency
- # list will get cleared and we'll re-scan the newly-built
- # file(s) for updated implicit dependencies.
- added = map(lambda n, P=node: n.add_to_waiting_parents(P), not_started)
- node.ref_count = node.ref_count + reduce(operator.add, added, 0)
-
- # Now we add these derived targets to the candidates
- # list so they can be examined and built. We have to
- # add ourselves back to the list first, though, so we get
- # a chance to re-scan and build after the dependencies.
- #
- # We reverse the order in which the children are added
- # to the candidates stack so the order in which they're
- # popped matches the order in which they show up in our
- # children's list. This is more logical / efficient for
- # builders with multiple targets, since the "primary"
- # target will be examined first.
- self.candidates.append(node)
- not_started.reverse()
- self.candidates.extend(self.order(not_started))
-
- if S: S.not_started = S.not_started + 1
- if T:
- c = map(str, not_started)
- c.sort()
- T.write(' waiting on unstarted children:\n %s\n' % c)
- continue
-
- not_built = filter(lambda I: I[0] <= SCons.Node.executing, derived_children)
+ not_built = filter(lambda I: I[1] <= SCons.Node.executing, childstate)
if not_built:
- not_built = map(lambda I: I[2], not_built)
-
# We're waiting on one or more derived targets that have
- # started building but not yet finished. Add this node
- # to the waiting parents lists of those derived files
- # so that when they've finished building, our implicit
- # dependency list will get cleared and we'll re-scan the
- # newly-built file(s) for updated implicit dependencies.
- added = map(lambda n, P=node: n.add_to_waiting_parents(P), not_built)
- node.ref_count = node.ref_count + reduce(operator.add, added, 0)
+ # not yet finished building.
+
+ not_visited = filter(lambda I: not I[1], not_built)
+ if not_visited:
+ # Some of them haven't even been visited yet.
+ # Add them to the list so that on some next pass
+ # we can take a stab at evaluating them (or
+ # their children).
+ not_visited = map(lambda I: I[0], not_visited)
+ not_visited.reverse()
+ self.candidates.extend(self.order(not_visited))
+
+ n_b_nodes = map(lambda I: I[0], not_built)
+
+ # Add this node to the waiting parents lists of anything
+ # we're waiting on, with a reference count so we can be
+ # put back on the list for re-evaluation when they've
+ # all finished.
+ map(lambda n, P=node: n.add_to_waiting_parents(P), n_b_nodes)
+ node.ref_count = len(set(n_b_nodes))
if S: S.not_built = S.not_built + 1
if T:
- c = map(str, not_built)
+ c = map(str, n_b_nodes)
c.sort()
T.write(' waiting on unfinished children:\n %s\n' % c)
continue
- # Skip this node if it has side-effects that are currently being
- # built themselves or waiting for something else being built.
+ # Skip this node if it has side-effects that are
+ # currently being built:
side_effects = filter(lambda N:
N.get_state() == SCons.Node.executing,
node.side_effects)
tlist = node.get_executor().targets
- task = self.tasker(self, tlist, node is self.current_top, node)
+ task = self.tasker(self, tlist, node in self.original_top, node)
try:
task.make_ready()
except KeyboardInterrupt:
cache_text.append(self.name + " retrieved")
return self.cached
+ def make_ready(self):
+ pass
+
+ def prepare(self):
+ self.prepared = 1
+
def build(self):
global built_text
built_text = self.name + " built"
+ def built(self):
+ global built_text
+ built_text = built_text + " really"
+
def has_builder(self):
return not self.builder is None
def alter_targets(self):
return self.alttargets, None
- def built(self):
- global built_text
- built_text = built_text + " really"
-
def visited(self):
global visited_nodes
visited_nodes.append(self.name)
- def prepare(self):
- self.prepared = 1
-
def children(self):
if not self.scanned:
self.scan()
def store_bsig(self):
pass
- def calculator(self):
- class Calc:
- def bsig(self, node):
- return node._bsig_val
- def current(self, node, sig):
- return node._current_val
- return Calc()
-
- def current(self, calc=None):
- if calc is None:
- calc = self.calculator()
- return calc.current(self, calc.bsig(self))
+ def is_pseudo_derived(self):
+ pass
+
+ def is_up_to_date(self):
+ return self._current_val
def depends_on(self, nodes):
for node in nodes:
s = n1.get_state()
assert s == SCons.Node.executed, s
assert built_text == "xxx really", built_text
- assert visited_nodes == [], visited_nodes
+ assert visited_nodes == ['n1'], visited_nodes
n2 = Node("n2")
tm = SCons.Taskmaster.Taskmaster([n2])
assert s == SCons.Node.up_to_date, s
s = n4.get_state()
assert s == SCons.Node.executed, s
- assert visited_nodes == ['n3'], visited_nodes
+ assert visited_nodes == ['n3', 'n4'], visited_nodes
def test_prepare(self):
"""Test preparation of multiple Nodes for a task
t = tm.next_task()
t.prepare()
t.execute()
+ t.postprocess()
n1.set_state(SCons.Node.executed)
t = tm.next_task()
t.prepare()
t.execute()
+ t.postprocess()
+ n2.set_state(SCons.Node.executed)
+ t = tm.next_task()
+ t.prepare()
+ t.execute()
+ t.postprocess()
t = tm.next_task()
+ assert t is None
value = trace.getvalue()
expect = """\
Taskmaster: 'n1': already handled (executed)
Taskmaster: 'n3': children:
['n1', 'n2']
- waiting on unstarted children:
+ waiting on unfinished children:
['n2']
Taskmaster: 'n2': evaluating n2
Taskmaster: 'n3': children:
['n1', 'n2']
- waiting on unfinished children:
- ['n2']
+ evaluating n3
"""
assert value == expect, value
# semi-colons;
# periods.
_reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' +
- r'[A-Za-z_][\w\.]*|/\*|\*/|\[\])')
+ r'[A-Za-z_][\w\$\.]*|/\*|\*/|\[\])')
class OuterState:
"""The initial state for parsing a Java file for classes,
assert classes == ['Foo'], classes
+
+ def test_dollar_sign(self):
+ """Test class names with $ in them"""
+
+ input = """\
+public class BadDep {
+ public void new$rand () {}
+}
+"""
+ pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
+ assert pkg_dir == None, pkg_dir
+ assert classes == ['BadDep'], classes
+
+
+
def test_inner_classes(self):
"""Test parsing various forms of inner classes"""
env['JAVASUFFIX'] = '.java'
return java_file
-
+class ToolInitializer:
+ """
+ A class for delayed initialization of Tools modules.
+
+ This is intended to be added to a construction environment in
+ place of the method(s) normally called for a Builder (env.Object,
+ env.StaticObject, etc.). When called, it searches the specified
+ list of tools, applies the first one that exists to the construction
+ environment, and calls whatever builder was (presumably) added the
+ construction environment in our place.
+ """
+ def __init__(self, name, tools):
+ """
+ Note: we store the tool name as __name__ so it can be used by
+ the class that attaches this to a construction environment.
+ """
+ self.__name__ = name
+ if not SCons.Util.is_List(tools):
+ tools = [tools]
+ self.tools = tools
+ def __call__(self, env, *args, **kw):
+ for t in self.tools:
+ tool = SCons.Tool.Tool(t)
+ if tool.exists(env):
+ env.Tool(tool)
+ break
+
+ builder = getattr(env, self.__name__)
+ if builder is self:
+ # There was no Builder added, which means no valid Tool
+ # for this name was found (or possibly there's a mismatch
+ # between the name we were called by and the Builder name
+ # added by the Tool module).
+ #
+ # (Eventually this is where we'll put a more informative
+ # error message about the inability to find that tool
+ # as cut over more Builders+Tools to using this.
+ return [], []
+
+ # Let the construction environment remove the added method
+ # so we no longer copy and re-bind this method when the
+ # construction environment gets cloned.
+ env.RemoveMethod(self)
+ return apply(builder, args, kw)
+
+def Initializers(env):
+ env.AddMethod(ToolInitializer('Install', 'install'))
+ env.AddMethod(ToolInitializer('InstallAs', 'install'))
def FindTool(tools, env):
for tool in tools:
other_tools = FindAllTools(['BitKeeper', 'CVS',
'dmd',
- 'install', 'filesystem',
+ 'filesystem',
'dvipdf', 'dvips', 'gs',
'jar', 'javac', 'javah',
'latex', 'lex',
#
# We keep track of *all* installed files.
_INSTALLED_FILES = []
+_UNIQUE_INSTALLED_FILES = None
#
# Functions doing the actual work of the Install Builder.
_INSTALLED_FILES global variable. This way all installed files of one
scons call will be collected.
"""
- global _INSTALLED_FILES
- files = _INSTALLED_FILES
- #files.extend( [ x for x in target if not x in files ] )
- for x in target:
- if not x in files:
- files.append(x)
+ global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
+ _INSTALLED_FILES.extend(target)
+ _UNIQUE_INSTALLED_FILES = None
return (target, source)
class DESTDIR_factory:
install_action = SCons.Action.Action(installFunc, stringFunc)
installas_action = SCons.Action.Action(installFunc, stringFunc)
-InstallBuilder, InstallAsBuilder = None, None
BaseInstallBuilder = None
+def InstallBuilderWrapper(env, target, source, dir=None):
+ if target and dir:
+ raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
+ if not dir:
+ dir=target
+
+ import SCons.Script
+ install_sandbox = SCons.Script.GetOption('install_sandbox')
+ if install_sandbox:
+ target_factory = DESTDIR_factory(env, install_sandbox)
+ else:
+ target_factory = env.fs
+
+ try:
+ dnodes = env.arg2nodes(dir, target_factory.Dir)
+ except TypeError:
+ raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir)
+ sources = env.arg2nodes(source, env.fs.Entry)
+ tgt = []
+ for dnode in dnodes:
+ for src in sources:
+ # Prepend './' so the lookup doesn't interpret an initial
+ # '#' on the file name portion as meaning the Node should
+ # be relative to the top-level SConstruct directory.
+ target = env.fs.Entry('.'+os.sep+src.name, dnode)
+ tgt.extend(BaseInstallBuilder(env, target, src))
+ return tgt
+
+def InstallAsBuilderWrapper(env, target, source):
+ result = []
+ for src, tgt in map(lambda x, y: (x, y), source, target):
+ result.extend(BaseInstallBuilder(env, tgt, src))
+ return result
+
added = None
def generate(env):
action="store",
help='A directory under which all installed files will be placed.')
- try:
- env['BUILDERS']['Install']
- env['BUILDERS']['InstallAs']
-
- except KeyError, e:
+ global BaseInstallBuilder
+ if BaseInstallBuilder is None:
install_sandbox = GetOption('install_sandbox')
if install_sandbox:
target_factory = DESTDIR_factory(env, install_sandbox)
else:
target_factory = env.fs
- global BaseInstallBuilder
- if BaseInstallBuilder is None:
- BaseInstallBuilder = SCons.Builder.Builder(
- action = install_action,
- target_factory = target_factory.Entry,
- source_factory = env.fs.Entry,
- multi = 1,
- emitter = [ add_targets_to_INSTALLED_FILES, ],
- name = 'InstallBuilder')
-
- global InstallBuilder
- if InstallBuilder is None:
- def InstallBuilderWrapper(env, target, source, dir=None, target_factory=target_factory):
- if target and dir:
- raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined."
- if not dir:
- dir=target
- try:
- dnodes = env.arg2nodes(dir, target_factory.Dir)
- except TypeError:
- raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir)
- sources = env.arg2nodes(source, env.fs.Entry)
- tgt = []
- for dnode in dnodes:
- for src in sources:
- # Prepend './' so the lookup doesn't interpret an initial
- # '#' on the file name portion as meaning the Node should
- # be relative to the top-level SConstruct directory.
- target = env.fs.Entry('.'+os.sep+src.name, dnode)
- tgt.extend(BaseInstallBuilder(env, target, src))
- return tgt
-
- InstallBuilder = InstallBuilderWrapper
-
- global InstallAsBuilder
- if InstallAsBuilder is None:
- def InstallAsBuilderWrapper(env, target, source):
- result = []
- for src, tgt in map(lambda x, y: (x, y), source, target):
- result.extend(BaseInstallBuilder(env, tgt, src))
- return result
-
- InstallAsBuilder = InstallAsBuilderWrapper
-
- env['BUILDERS']['Install'] = InstallBuilder
- env['BUILDERS']['InstallAs'] = InstallAsBuilder
+ BaseInstallBuilder = SCons.Builder.Builder(
+ action = install_action,
+ target_factory = target_factory.Entry,
+ source_factory = env.fs.Entry,
+ multi = 1,
+ emitter = [ add_targets_to_INSTALLED_FILES, ],
+ name = 'InstallBuilder')
+
+ try:
+ env['BUILDERS']['Install']
+ except KeyError, e:
+ env['BUILDERS']['Install'] = InstallBuilderWrapper
+
+ try:
+ env['BUILDERS']['InstallAs']
+ except KeyError, e:
+ env['BUILDERS']['InstallAs'] = InstallAsBuilderWrapper
# We'd like to initialize this doing something like the following,
# but there isn't yet support for a ${SOURCE.type} expansion that
import math, sys, os.path, glob, string, re
is_windows = sys.platform == 'win32'
+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'
if is_windows:
Return a value from the Intel compiler registry tree. (Windows only)
"""
# Open the key:
- K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+ if is_win64:
+ K = 'Software\\Wow6432Node\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+ else:
+ K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
except SCons.Util.RegError:
"""
versions=[]
if is_windows:
- keyname = 'Software\\Intel\\Compilers\\C++'
+ if is_win64:
+ keyname = 'Software\\WoW6432Node\\Intel\\Compilers\\C++'
+ else:
+ keyname = 'Software\\Intel\\Compilers\\C++'
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
keyname)
# than uninstalling properly), so the registry values
# are still there.
ok = False
- for try_abi in ('IA32', 'IA32e', 'IA64'):
+ for try_abi in ('IA32', 'IA32e', 'IA64', 'EM64T'):
try:
d = get_intel_registry_value('ProductDir', subkey, try_abi)
except MissingRegistryError:
The compiler will be in <top>/bin/icl.exe (icc on linux),
the include dir is <top>/include, etc.
"""
+
if is_windows:
if not SCons.Util.can_read_reg:
raise NoRegistryModuleError, "No Windows registry module was found"
else:
abi = 'ia32'
else:
- # XXX: how would we do the same test on Windows?
- abi = "ia32"
+ if is_win64:
+ abi = 'em64t'
+ else:
+ abi = 'ia32'
if version and not topdir:
try:
target_factory = SCons.Node.FS.Entry,
source_factory = SCons.Node.FS.Entry)
-def getClassPath(env,target, source, for_signature):
- path = ""
- if env.has_key('JAVACLASSPATH') and env['JAVACLASSPATH']:
- path = SCons.Util.AppendPath(path, env['JAVACLASSPATH'])
- return "-classpath %s" % (path)
- else:
- return ""
-
-def getSourcePath(env,target, source, for_signature):
- path = ""
- if env.has_key('JAVASOURCEPATH') and env['JAVASOURCEPATH']:
- path = SCons.Util.AppendPath(path, env['JAVASOURCEPATH'])
- path = SCons.Util.AppendPath(path,['${TARGET.attributes.java_sourcedir}'])
- return "-sourcepath %s" % (path)
+class pathopt:
+ """
+ Callable object for generating javac-style path options from
+ a construction variable (e.g. -classpath, -sourcepath).
+ """
+ def __init__(self, opt, var, default=None):
+ self.opt = opt
+ self.var = var
+ self.default = default
+
+ def __call__(self, target, source, env, for_signature):
+ path = env[self.var]
+ if path and not SCons.Util.is_List(path):
+ path = [path]
+ if self.default:
+ path = path + [ env[self.default] ]
+ if path:
+ return [self.opt, string.join(path, os.pathsep)]
+ #return self.opt + " " + string.join(path, os.pathsep)
+ else:
+ return []
+ #return ""
def Java(env, target, source, *args, **kw):
"""
env.AddMethod(Java)
- env['JAVAC'] = 'javac'
- env['JAVACFLAGS'] = SCons.Util.CLVar('')
- env['JAVACLASSPATH'] = []
- env['JAVASOURCEPATH'] = []
- env['_JAVACLASSPATH'] = getClassPath
- env['_JAVASOURCEPATH'] = getSourcePath
- env['_JAVACCOM'] = '$JAVAC $JAVACFLAGS $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES'
- env['JAVACCOM'] = "${TEMPFILE('$_JAVACCOM')}"
- env['JAVACLASSSUFFIX'] = '.class'
- env['JAVASUFFIX'] = '.java'
+ env['JAVAC'] = 'javac'
+ env['JAVACFLAGS'] = SCons.Util.CLVar('')
+ env['JAVABOOTCLASSPATH'] = []
+ env['JAVACLASSPATH'] = []
+ env['JAVASOURCEPATH'] = []
+ env['_javapathopt'] = pathopt
+ env['_JAVABOOTCLASSPATH'] = '${_javapathopt("-bootclasspath", "JAVABOOTCLASSPATH")} '
+ env['_JAVACLASSPATH'] = '${_javapathopt("-classpath", "JAVACLASSPATH")} '
+ env['_JAVASOURCEPATH'] = '${_javapathopt("-sourcepath", "JAVASOURCEPATH", "_JAVASOURCEPATHDEFAULT")} '
+ env['_JAVASOURCEPATHDEFAULT'] = '${TARGET.attributes.java_sourcedir}'
+ env['_JAVACCOM'] = '$JAVAC $JAVACFLAGS $_JAVABOOTCLASSPATH $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES'
+ env['JAVACCOM'] = "${TEMPFILE('$_JAVACCOM')}"
+ env['JAVACLASSSUFFIX'] = '.class'
+ env['JAVASUFFIX'] = '.java'
def exists(env):
return 1
JAVACCOM
JAVACLASSSUFFIX
JAVASUFFIX
+JAVABOOTCLASSPATH
JAVACLASSPATH
JAVASOURCEPATH
</sets>
</summary>
</builder>
+<cvar name="JAVABOOTCLASSPATH">
+<summary>
+Specifies the list of directories that
+will be added to the
+&javac; command line
+via the <option>-bootclasspath</option> option.
+The individual directory names will be
+separated by the operating system's path separate character
+(<filename>:</filename> on UNIX/Linux/POSIX,
+<filename>;</filename> on Windows).
+</summary>
+</cvar>
+
<cvar name="JAVAC">
<summary>
The Java compiler.
import SCons.Defaults
import SCons.Scanner.IDL
import SCons.Util
-import SCons.Tool.msvs
def midl_emitter(target, source, env):
"""Produces a list of outputs from the MIDL compiler"""
env['BUILDERS']['TypeLibrary'] = midl_builder
def exists(env):
+ if not env['PLATFORM'] in ('win32', 'cygwin'):
+ return 0
+
+ import SCons.Tool.msvs
if SCons.Tool.msvs.is_msvs_installed():
# there's at least one version of MSVS installed, which comes with midl:
return 1
env['SCONS_HOME'] = os.environ.get('SCONS_HOME')
def exists(env):
+ if not env['PLATFORM'] in ('win32', 'cygwin'):
+ return 0
+
try:
v = SCons.Tool.msvs.get_visualstudio_versions()
except (SCons.Util.RegError, SCons.Errors.InternalError):
# only makes sense to test this on win32
if sys.platform != 'win32':
+ sys.stdout.write("NO RESULT for msvsTests.py: '%s' is not win32\n" % sys.platform)
sys.exit(0)
SCons.Util.RegOpenKeyEx = DummyOpenKeyEx
from SCons.Options import *
from SCons.Errors import *
from SCons.Util import is_List, make_path_relative
+from SCons.Warnings import warn, Warning
import os, imp
import SCons.Defaults
try: kw['PACKAGETYPE']=env['PACKAGETYPE']
except KeyError: pass
- if not kw.has_key('PACKAGETYPE') or kw['PACKAGETYPE']==None:
+ if not kw.get('PACKAGETYPE'):
+ from SCons.Script import GetOption
+ kw['PACKAGETYPE'] = GetOption('package_type')
+
+ if kw['PACKAGETYPE'] == None:
if env['BUILDERS'].has_key('Tar'):
kw['PACKAGETYPE']='targz'
elif env['BUILDERS'].has_key('Zip'):
targets.extend(env.Alias( 'package', targets ))
return targets
-def build_source(ss, sources):
- for s in ss:
- if s.__class__==SCons.Node.FS.Dir:
- build_source(s.all_children())
- elif not s.has_builder() and s.__class__==SCons.Node.FS.File:
- sources.append(s)
- else:
- build_source(s.sources)
-
-def FindSourceFiles(env, target=None, source=None ):
- """ returns a list of all children of the target nodes, which have no
- children. This selects all leaves of the DAG that gets build by SCons for
- handling dependencies.
- """
- if target==None: target = '.'
-
- nodes = env.arg2nodes(target, env.fs.Entry)
-
- sources = []
- for node in nodes:
- build_source(node.all_children(), sources)
-
- # now strip the build_node from the sources by calling the srcnode
- # function
- def get_final_srcnode(file):
- srcnode = file.srcnode()
- while srcnode != file.srcnode():
- srcnode = file.srcnode()
- return srcnode
-
- # get the final srcnode for all nodes, this means stripping any
- # attached build node.
- map( get_final_srcnode, sources )
-
- # remove duplicates
- return list(set(sources))
-
-def FindInstalledFiles(env, source=[], target=[]):
- """ returns the list of all targets of the Install and InstallAs Builder.
- """
- from SCons.Tool import install
- return install._INSTALLED_FILES
-
#
# SCons tool initialization functions
#
+
+added = None
+
def generate(env):
+ from SCons.Script import AddOption
+ global added
+ if not added:
+ added = 1
+ AddOption('--package-type',
+ dest='package_type',
+ default=None,
+ type="string",
+ action="store",
+ help='The type of package to create.')
+
try:
env['BUILDERS']['Package']
env['BUILDERS']['Tag']
- env['BUILDERS']['FindSourceFiles']
- env['BUILDERS']['FindInstalledFiles']
except KeyError:
env['BUILDERS']['Package'] = Package
env['BUILDERS']['Tag'] = Tag
- env['BUILDERS']['FindSourceFiles'] = FindSourceFiles
- env['BUILDERS']['FindInstalledFiles'] = FindInstalledFiles
def exists(env):
return 1
+# XXX
def options(opts):
opts.AddOptions(
- EnumOption( [ 'PACKAGETYPE', '--package-type' ],
+ EnumOption( 'PACKAGETYPE',
'the type of package to create.',
None, allowed_values=map( str, __all__ ),
ignorecase=2
)
)
+#
+# Internal utility functions
+#
+
def copy_attr(f1, f2):
""" copies the special packaging file attributes from f1 to f2.
"""
pattrs = filter(copyit, dir(f1))
for attr in pattrs:
setattr(f2, attr, getattr(f1, attr))
-#
-# Emitter functions which are reused by the various packagers
-#
-def packageroot_emitter(pkg_root, honor_install_location=1):
- """ creates the packageroot emitter.
-
- The package root emitter uses the CopyAs builder to copy all source files
- to the directory given in pkg_root.
+def putintopackageroot(target, source, env, pkgroot, honor_install_location=1):
+ """ Uses the CopyAs builder to copy all source files to the directory given
+ in pkgroot.
If honor_install_location is set and the copied source file has an
PACKAGING_INSTALL_LOCATION attribute, the PACKAGING_INSTALL_LOCATION is
- used as the new name of the source file under pkg_root.
+ used as the new name of the source file under pkgroot.
- The source file will not be copied if it is already under the the pkg_root
+ The source file will not be copied if it is already under the the pkgroot
directory.
All attributes of the source file will be copied to the new file.
"""
- def package_root_emitter(target, source, env, pkg_root=pkg_root, honor_install_location=honor_install_location):
- pkgroot = pkg_root
- # make sure the packageroot is a Dir object.
- if SCons.Util.is_String(pkgroot): pkgroot=env.Dir(pkgroot)
-
- def copy_file_to_pkg_root(file, env=env, pkgroot=pkgroot, honor_install_location=honor_install_location):
- if file.is_under(pkgroot):
- return file
- else:
- if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
- honor_install_location:
- new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
- else:
- new_name=make_path_relative(file.get_path())
+ # make sure the packageroot is a Dir object.
+ if SCons.Util.is_String(pkgroot): pkgroot=env.Dir(pkgroot)
+ if not SCons.Util.is_List(source): source=[source]
- new_file=pkgroot.File(new_name)
- new_file=env.CopyAs(new_file, file)[0]
+ new_source = []
+ for file in source:
+ if SCons.Util.is_String(file): file = env.File(file)
- copy_attr(file, new_file)
+ if file.is_under(pkgroot):
+ new_source.append(file)
+ else:
+ if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
+ honor_install_location:
+ new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
+ else:
+ new_name=make_path_relative(file.get_path())
- return new_file
- return (target, map(copy_file_to_pkg_root, source))
- return package_root_emitter
+ new_file=pkgroot.File(new_name)
+ new_file=env.CopyAs(new_file, file)[0]
+ copy_attr(file, new_file)
+ new_source.append(new_file)
-from SCons.Warnings import warn, Warning
+ return (target, new_source)
-def stripinstall_emitter():
- """ create the a emitter which:
- * strips of the Install Builder of the source target, and stores the
- install location as the "PACKAGING_INSTALL_LOCATION" of the given source
- File object. This effectively avoids having to execute the Install
- Action while storing the needed install location.
- * warns about files that are mangled by this emitter which have no
- Install Builder.
+def stripinstallbuilder(target, source, env):
+ """ strips the install builder action from the source list and stores
+ the final installation location as the "PACKAGING_INSTALL_LOCATION" of
+ the source of the source file. This effectively removes the final installed
+ files from the source list while remembering the installation location.
+
+ It also warns about files which have no install builder attached.
"""
- def strip_install_emitter(target, source, env):
- def has_no_install_location(file):
- return not (file.has_builder() and\
- hasattr(file.builder, 'name') and\
- (file.builder.name=="InstallBuilder" or\
- file.builder.name=="InstallAsBuilder"))
-
- if len(filter(has_no_install_location, source)):
- warn(Warning, "there are file to package which have no\
- InstallBuilder attached, this might lead to irreproducible packages")
-
- n_source=[]
- for s in source:
- if has_no_install_location(s):
- n_source.append(s)
- else:
- for ss in s.sources:
- n_source.append(ss)
- copy_attr(s, ss)
- setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
+ def has_no_install_location(file):
+ return not (file.has_builder() and\
+ hasattr(file.builder, 'name') and\
+ (file.builder.name=="InstallBuilder" or\
+ file.builder.name=="InstallAsBuilder"))
+
+ if len(filter(has_no_install_location, source)):
+ warn(Warning, "there are files to package which have no\
+ InstallBuilder attached, this might lead to irreproducible packages")
+
+ n_source=[]
+ for s in source:
+ if has_no_install_location(s):
+ n_source.append(s)
+ else:
+ for ss in s.sources:
+ n_source.append(ss)
+ copy_attr(s, ss)
+ setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
- return (target, n_source)
- return strip_install_emitter
+ return (target, n_source)
import SCons.Node.FS
import os
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
def package(env, target, source, PACKAGEROOT, NAME, VERSION, DESCRIPTION,
SUMMARY, X_IPK_PRIORITY, X_IPK_SECTION, SOURCE_URL,
# setup the Ipkg builder
bld = env['BUILDERS']['Ipkg']
- bld.push_emitter(packageroot_emitter(PACKAGEROOT))
- bld.push_emitter(stripinstall_emitter())
+ target, source = stripinstallbuilder(target, source, env)
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT)
# This should be overridable from the construction environment,
# which it is by using ARCHITECTURE=.
# create the specfile builder
s_bld=SCons.Builder.Builder(
action = build_specfiles,
- emitter = [stripinstall_emitter(), packageroot_emitter(proot)])
+ )
# create the specfile targets
spec_target=[]
from xml.dom.minidom import *
from xml.sax.saxutils import escape
-from SCons.Tool.packaging import stripinstall_emitter
+from SCons.Tool.packaging import stripinstallbuilder
#
# Utility functions
#
# setup function
#
-def create_default_directory_layout(root, NAME, VERSION, vendor, filename_set):
+def create_default_directory_layout(root, NAME, VERSION, VENDOR, filename_set):
""" Create the wix default target directory layout and return the innermost
directory.
the Product tag.
Everything is put under the PFiles directory property defined by WiX.
- After that a directory with the 'vendor' tag is placed and then a
+ After that a directory with the 'VENDOR' tag is placed and then a
directory with the name of the project and its VERSION. This leads to the
following TARGET Directory Layout:
C:\<PFiles>\<Vendor>\<Projectname-Version>\
d2.attributes['Name'] = 'PFiles'
d3 = doc.createElement( 'Directory' )
- d3.attributes['Id'] = 'vendor_folder'
- d3.attributes['Name'] = escape( gen_dos_short_file_name( vendor, filename_set ) )
- d3.attributes['LongName'] = escape( vendor )
+ d3.attributes['Id'] = 'VENDOR_folder'
+ d3.attributes['Name'] = escape( gen_dos_short_file_name( VENDOR, filename_set ) )
+ d3.attributes['LongName'] = escape( VENDOR )
d4 = doc.createElement( 'Directory' )
project_folder = "%s-%s" % ( NAME, VERSION )
#
# mandatory and optional file tags
#
-def build_wxsfile_file_section(root, files, NAME, VERSION, vendor, filename_set, id_set):
+def build_wxsfile_file_section(root, files, NAME, VERSION, VENDOR, filename_set, id_set):
""" builds the Component sections of the wxs file with their included files.
Files need to be specified in 8.3 format and in the long name format, long
Features are specficied with the 'X_MSI_FEATURE' or 'DOC' FileTag.
"""
- root = create_default_directory_layout( root, NAME, VERSION, vendor, filename_set )
+ root = create_default_directory_layout( root, NAME, VERSION, VENDOR, filename_set )
components = create_feature_dict( files )
factory = Document()
# mandatory sections, will throw a KeyError if the tag is not available
Product.attributes['Name'] = escape( spec['NAME'] )
Product.attributes['Version'] = escape( spec['VERSION'] )
- Product.attributes['Manufacturer'] = escape( spec['vendor'] )
+ Product.attributes['Manufacturer'] = escape( spec['VENDOR'] )
Product.attributes['Language'] = escape( spec['X_MSI_LANGUAGE'] )
Package.attributes['Description'] = escape( spec['SUMMARY'] )
# this builder is the entry-point for .wxs file compiler.
wxs_builder = Builder(
- action = Action( build_wxsfile, string_wxsfile ),
- emitter = stripinstall_emitter(),
- suffix = '.wxs' )
+ action = Action( build_wxsfile, string_wxsfile ),
+ ensure_suffix = '.wxs' )
def package(env, target, source, PACKAGEROOT, NAME, VERSION,
- DESCRIPTION, SUMMARY, **kw):
+ DESCRIPTION, SUMMARY, VENDOR, X_MSI_LANGUAGE, **kw):
# make sure that the Wix Builder is in the environment
SCons.Tool.Tool('wix').generate(env)
kw.update(loc)
del kw['source'], kw['target'], kw['env']
+ # strip the install builder from the source files
+ target, source = stripinstallbuilder(target, source, env)
+
# put the arguments into the env and call the specfile builder.
env['msi_spec'] = kw
specfile = apply( wxs_builder, [env, target, source], kw )
+++ /dev/null
-"""SCons.Tool.Packaging.packager
-"""
-
-#
-# __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 SCons.Defaults
-from SCons.Util import strip_abs_path
-
-class Packager:
- """ abstract superclass of all packagers.
-
- Defines the minimal set of function which need to be implemented in order
- to create a new packager.
- """
- def __init__(self):
- self.specfile_suffix = '.spec'
-
- def create_builder(self, env, kw):
- raise Exception( "%s does not implement create_builder()" % (self.__class__.__name_) )
-
- def add_targets(self, kw):
- """ In the absence of a target this method creates a default one of
- the spec given in the kw argument.
- """
- if not kw.has_key('target'):
- NAME, VERSION = kw['projectname'], kw['version']
- kw['target'] = [ "%s-%s"%(NAME,VERSION) ]
-
- return kw
-
- def strip_install_emitter(self, target, source, env):
- """ this emitter assert that all files in source have an InstallBuilder
- attached. We take their sources and copy over the file tags, so the
- the builder this emitter is attached to is independent of the *installed*
- files.
- """
- tag_factories = [ LocationTagFactory() ]
-
- def has_no_install_location(file):
- return not ( file.has_builder() and (file.builder.name == 'InstallBuilder' or file.builder.name == 'InstallAsBuilder') )
-
- # check if all source file belong into this package.
- files = filter( has_no_install_location, source )
- if len( files ) != 0:
- raise SCons.Errors.UserError( "There are files which have no Install() Builder attached and are therefore not packageable\n%s" % map( lambda x: x.get_path(), files ) )
-
- # All source files have an InstallBuilder attached and we don't want our
- # package to be dependent on the *installed* files but only on the
- # files that will be installed. Therefore we only care for sources of
- # the files in the source list.
- n_source = []
-
- for s in source:
- n_s = s.sources[0]
- n_s.set_tags( s.get_tags(tag_factories) )
- n_source.append( n_s )
-
- return ( target, n_source )
-
-
-class BinaryPackager(Packager):
- """ abstract superclass for all packagers creating a binary package.
-
- Binary packagers are seperated from source packager by their requirement to
- create a specfile or manifest file. This file contains the contents of the
- binary packager together with some information about specific files.
-
- This superclass provides two needed facilities:
- * its specfile_emitter function sets up the correct list of source file
- and warns about files with no InstallBuilder attached.
- """
- def create_specfile_targets(self, kw):
- """ returns the specfile target name(s).
-
- This function is called by specfile_emitter to find out the specfiles
- target name.
- """
- p, v = kw['NAME'], kw['VERSION']
- return '%s-%s' % ( p, v )
-
- def specfile_emitter(self, target, source, env):
- """ adds the to build specfile to the source list.
- """
- # create a specfile action that is executed for building the specfile
- specfile_action = SCons.Action.Action( self.build_specfile,
- self.string_specfile,
- varlist=[ 'SPEC' ] )
-
- # create a specfile Builder with the right sources attached.
- specfile_builder = SCons.Builder.Builder( action = self.build_specfile,
- suffix = self.specfile_suffix )
-
- specfile = apply( specfile_builder, [ env ], {
- 'target' : self.create_specfile_targets(env),
- 'source' : source } )
-
- specfile.extend( source )
- return ( target, specfile )
-
- def string_specfile(self, target, source, env):
- return "building specfile %s"%(target[0].abspath)
-
- def build_specfile(self, target, source, env):
- """ this function is called to build the specfile of name "target"
- from the source list and the settings in "env"
- """
- raise Exception( 'class does not implement build_specfile()' )
-
-class SourcePackager(Packager):
- """ abstract superclass for all packagers which generate a source package.
-
- They are seperated from other packager by the their package_root attribute.
- Since before a source package is created with the help of a Tar or Zip
- builder their content needs to be moved to a package_root. For example the
- project foo with VERSION 1.2.3, will get its files placed in foo-1.2.3/.
- """
- def create_package_root(self,kw):
- """ creates the package_r oot for a given specification dict.
- """
- try:
- return kw['package_root']
- except KeyError:
- NAME, VERSION = kw['projectname'], kw['version']
- return "%s-%s"%(NAME,VERSION)
-
- def package_root_emitter(self, pkg_root, honor_install_location=1):
- def package_root_emitter(target, source, env):
- """ This emitter copies the sources to the src_package_root directory:
- * if a source has an install_location, not its original name is
- used but the one specified in the 'install_location' tag.
- * else its original name is used.
- * if the source file is already in the src_package_root directory,
- nothing will be done.
- """
- new_source = []
- for s in source:
- if os.path.dirname(s.get_path()).rfind(pkg_root) != -1:
- new_source.append(s)
- else:
- tags = s.get_tags()
- new_s = None
-
- if tags.has_key( 'install_location' ) and honor_install_location:
- my_target = strip_abs_path(tags['install_location'])
- else:
- my_target = strip_abs_path(s.get_path())
-
- new_s = env.CopyAs( os.path.join( pkg_root, my_target ), s )[0]
-
- # store the tags of our original file in the new file.
- new_s.set_tags( s.get_tags() )
- new_source.append( new_s )
-
- return (target, new_source)
-
- return package_root_emitter
-
-class TagFactory:
- """An instance of this class has the responsibility to generate additional
- tags for a SCons.Node.FS.File instance.
-
- Subclasses have to be callable. This class definition is informally
- describing the interface.
- """
-
- def __call__(self, file, current_tag_dict):
- """ This call has to return additional tags in the form of a dict.
- """
- pass
-
- def attach_additional_info(self, info=None):
- pass
-
-class LocationTagFactory(TagFactory):
- """ This class creates the "location" tag, which describes the install
- location of a given file.
-
- This is done by analyzing the builder of a given file for a InstallBuilder,
- from this builder the install location is deduced.
- """
-
- def __call__(self, file, current_tag_dict):
- if current_tag_dict.has_key('install_location'):
- return {}
-
- if file.has_builder() and\
- (file.builder.name == "InstallBuilder" or\
- file.builder.name == "InstallAsBuilder") and\
- file.has_explicit_builder():
- return { 'install_location' : file.get_path() }
- else:
- return {}
-
-
import SCons.Builder
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter, src_targz
+from SCons.Environment import OverrideEnvironment
+from SCons.Tool.packaging import stripinstallbuilder, src_targz
+from SCons.Errors import UserError
def package(env, target, source, PACKAGEROOT, NAME, VERSION,
PACKAGEVERSION, DESCRIPTION, SUMMARY, X_RPM_GROUP, LICENSE,
bld = env['BUILDERS']['Rpm']
- bld.push_emitter(targz_emitter)
- bld.push_emitter(specfile_emitter)
- bld.push_emitter(stripinstall_emitter())
-
- # override the default target, with the rpm specific ones.
- if str(target[0])=="%s-%s"%(NAME, VERSION):
+ # Generate a UserError whenever the target name has been set explicitly,
+ # since rpm does not allow for controlling it. This is detected by
+ # checking if the target has been set to the default by the Package()
+ # Environment function.
+ if str(target[0])!="%s-%s"%(NAME, VERSION):
+ raise UserError( "Setting target is not supported for rpm." )
+ else:
# This should be overridable from the construction environment,
# which it is by using ARCHITECTURE=.
# Guessing based on what os.uname() returns at least allows it
# if no "SOURCE_URL" tag is given add a default one.
if not kw.has_key('SOURCE_URL'):
- kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '')
+ #kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '')
+ kw['SOURCE_URL']=string.replace(str(target[0])+".tar.gz", '.rpm', '')
+
+ # mangle the source and target list for the rpmbuild
+ env = OverrideEnvironment(env, kw)
+ target, source = stripinstallbuilder(target, source, env)
+ target, source = addspecfile(target, source, env)
+ target, source = collectintargz(target, source, env)
# now call the rpm builder to actually build the packet.
return apply(bld, [env, target, source], kw)
-
-def targz_emitter(target, source, env):
+def collectintargz(target, source, env):
""" Puts all source files into a tar.gz file. """
# the rpm tool depends on a source package, until this is chagned
# this hack needs to be here that tries to pack all sources in.
return (target, tarball)
-def specfile_emitter(target, source, env):
+def addspecfile(target, source, env):
specfile = "%s-%s" % (env['NAME'], env['VERSION'])
bld = SCons.Builder.Builder(action = build_specfile,
# Default prep, build, install and clean rules
# TODO: optimize those build steps, to not compile the project a second time
if not spec.has_key('X_RPM_PREP'):
- spec['X_RPM_PREP'] = 'rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
+ spec['X_RPM_PREP'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
if not spec.has_key('X_RPM_BUILD'):
spec['X_RPM_BUILD'] = 'mkdir "$RPM_BUILD_ROOT"'
spec['X_RPM_INSTALL'] = 'scons --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"'
if not spec.has_key('X_RPM_CLEAN'):
- spec['X_RPM_CLEAN'] = 'rm -rf "$RPM_BUILD_ROOT"'
+ spec['X_RPM_CLEAN'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"'
str = str + SimpleTagCompiler(optional_sections, mandatory=0).compile( spec )
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Tar']
bld.set_suffix('.tar.bz2')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
return bld(env, target, source, TARFLAGS='-jc')
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Tar']
bld.set_suffix('.tar.gz')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
return bld(env, target, source, TARFLAGS='-zc')
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import packageroot_emitter
+from SCons.Tool.packaging import putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Zip']
bld.set_suffix('.zip')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT, honor_install_location=0))
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0)
return bld(env, target, source)
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Tar']
bld.set_suffix('.tar.gz')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT))
- bld.push_emitter(stripinstall_emitter())
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT)
+ target, source = stripinstallbuilder(target, source, env)
return bld(env, target, source, TARFLAGS='-jc')
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Tar']
bld.set_suffix('.tar.gz')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT))
- bld.push_emitter(stripinstall_emitter())
+ target, source = stripinstallbuilder(target, source, env)
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT)
return bld(env, target, source, TARFLAGS='-zc')
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-from SCons.Tool.packaging import stripinstall_emitter, packageroot_emitter
+from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot
def package(env, target, source, PACKAGEROOT, **kw):
bld = env['BUILDERS']['Zip']
bld.set_suffix('.zip')
- bld.push_emitter(packageroot_emitter(PACKAGEROOT))
- bld.push_emitter(stripinstall_emitter())
+ target, source = stripinstallbuilder(target, source, env)
+ target, source = putintopackageroot(target, source, env, PACKAGEROOT)
return bld(env, target, source)
rpmAction = SCons.Action.Action(build_rpm, string_rpm)
RpmBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$RPMCOM', '$RPMCOMSTR'),
- source_factory = SCons.Node.FS.Entry,
source_scanner = SCons.Defaults.DirScanner,
suffix = '$RPMSUFFIX')
env['CXX'] = 'CC'
env['CXXFLAGS'] = SCons.Util.CLVar('$CCFLAGS -LANG:std')
- env['SHCXX'] = 'CC'
+ env['SHCXX'] = '$CXX'
env['SHOBJSUFFIX'] = '.o'
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)
openout_aux_re = re.compile(r"\\openout.*`(.*\.aux)'")
+openout_re = re.compile(r"\\openout.*`(.*)'")
makeindex_re = re.compile(r"^[^%]*\\makeindex", re.MULTILINE)
tableofcontents_re = re.compile(r"^[^%]*\\tableofcontents", re.MULTILINE)
env.Precious(base + '.bbl')
target.append(base + '.blg')
- # read log file to get all .aux files
+ # read log file to get all output file (include .aux files)
logfilename = base + '.log'
dir, base_nodir = os.path.split(base)
if os.path.exists(logfilename):
content = open(logfilename, "rb").read()
- aux_files = openout_aux_re.findall(content)
- aux_files = filter(lambda f, b=base_nodir+'.aux': f != b, aux_files)
- aux_files = map(lambda f, d=dir: d+os.sep+f, aux_files)
- target.extend(aux_files)
- for a in aux_files:
- env.Precious( a )
+ out_files = openout_re.findall(content)
+ out_files = filter(lambda f, b=base_nodir+'.aux': f != b, out_files)
+ if dir != '':
+ out_files = map(lambda f, d=dir: d+os.sep+f, out_files)
+ target.extend(out_files)
+ for f in out_files:
+ env.Precious( f )
return (target, source)
env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"
object_builder = SCons.Builder.Builder(
- action = '$WIXCANDLECOM',
- suffix = '.wxiobj',
- src_suffix = '.wxs')
+ action = '$WIXCANDLECOM',
+ suffix = '.wxiobj',
+ src_suffix = '.wxs')
linker_builder = SCons.Builder.Builder(
- action = '$WIXLIGHTCOM',
+ action = '$WIXLIGHTCOM',
src_suffix = '.wxiobj',
src_builder = object_builder)
StringType = types.StringType
TupleType = types.TupleType
+def dictify(keys, values, result={}):
+ for k, v in zip(keys, values):
+ result[k] = v
+ return result
+
_altsep = os.altsep
if _altsep is None and sys.platform == 'win32':
# My ActivePython 2.0.1 doesn't set os.altsep! What gives?
tags.append(' S'[IDX(root.side_effect)])
tags.append(' P'[IDX(root.precious)])
tags.append(' A'[IDX(root.always_build)])
- tags.append(' C'[IDX(root.current())])
+ tags.append(' C'[IDX(root.is_up_to_date())])
tags.append(' N'[IDX(root.noclean)])
tags.append(' H'[IDX(root.nocache)])
tags.append(']')
def case_sensitive_suffixes(s1, s2):
return (os.path.normcase(s1) != os.path.normcase(s2))
-def adjustixes(fname, pre, suf):
+def adjustixes(fname, pre, suf, ensure_suffix=False):
if pre:
path, fn = os.path.split(os.path.normpath(fname))
if fn[:len(pre)] != pre:
fname = os.path.join(path, pre + fn)
- # Only append a suffix if the file does not have one.
- if suf and not splitext(fname)[1] and fname[-len(suf):] != suf:
+ # Only append a suffix if the suffix we're going to add isn't already
+ # there, and if either we've been asked to ensure the specific suffix
+ # is present or there's no suffix on it at all.
+ if suf and fname[-len(suf):] != suf and \
+ (ensure_suffix or not splitext(fname)[1]):
fname = fname + suf
return fname
+
+# From Tim Peters,
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
+# ASPN: Python Cookbook: Remove duplicates from a sequence
+# (Also in the printed Python Cookbook.)
+
def unique(s):
"""Return a list of the elements in s, but without duplicates.
u.append(x)
return u
+
+
+# From Alex Martelli,
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
+# ASPN: Python Cookbook: Remove duplicates from a sequence
+# First comment, dated 2001/10/13.
+# (Also in the printed Python Cookbook.)
+
+def uniquer(seq, idfun=None):
+ if idfun is None:
+ def idfun(x): return x
+ seen = {}
+ result = []
+ for item in seq:
+ marker = idfun(item)
+ # in old Python versions:
+ # if seen.has_key(marker)
+ # but in new ones:
+ if marker in seen: continue
+ seen[marker] = 1
+ result.append(item)
+ return result
+
+# A more efficient implementation of Alex's uniquer(), this avoids the
+# idfun() argument and function-call overhead by assuming that all
+# items in the sequence are hashable.
+
+def uniquer_hashables(seq):
+ seen = {}
+ result = []
+ for item in seq:
+ #if not item in seen:
+ if not seen.has_key(item):
+ seen[item] = 1
+ result.append(item)
+ return result
+
+
+
# Much of the logic here was originally based on recipe 4.9 from the
# Python CookBook, but we had to dumb it way down for Python 1.5.2.
class LogicalLines:
result.append(line)
return result
+
+
class Unbuffered:
"""
A proxy class that wraps a file object, flushing after every write,
func_defaults)
+md5 = False
+def MD5signature(s):
+ return str(s)
+
+try:
+ import hashlib
+except ImportError:
+ pass
+else:
+ if hasattr(hashlib, 'md5'):
+ md5 = True
+ def MD5signature(s):
+ m = hashlib.md5()
+ m.update(str(s))
+ return m.hexdigest()
+
+def MD5collect(signatures):
+ """
+ Collects a list of signatures into an aggregate signature.
+
+ signatures - a list of signatures
+ returns - the aggregate signature
+ """
+ if len(signatures) == 1:
+ return signatures[0]
+ else:
+ return MD5signature(string.join(signatures, ', '))
+
+
# From Dinu C. Gherman,
# Python Cookbook, second edition, recipe 6.17, p. 277.
def write(self, str):
self.buffer = self.buffer + str
+class dictifyTestCase(unittest.TestCase):
+ def test_dictify(self):
+ """Test the dictify() function"""
+ r = SCons.Util.dictify(['a', 'b', 'c'], [1, 2, 3])
+ assert r == {'a':1, 'b':2, 'c':3}, r
+
+ r = {}
+ SCons.Util.dictify(['a'], [1], r)
+ SCons.Util.dictify(['b'], [2], r)
+ SCons.Util.dictify(['c'], [3], r)
+ assert r == {'a':1, 'b':2, 'c':3}, r
+
class UtilTestCase(unittest.TestCase):
def test_splitext(self):
assert splitext('foo') == ('foo','')
return 1
def always_build(self):
return 1
- def current(self):
+ def is_up_to_date(self):
return 1
def noclean(self):
return 1
'bling\n',
], lines
+class MD5TestCase(unittest.TestCase):
+
+ def test_collect(self):
+ """Test collecting a list of signatures into a new signature value
+ """
+ s = map(MD5signature, ('111', '222', '333'))
+
+ assert '698d51a19d8a121ce581499d7b701668' == MD5collect(s[0:1])
+ assert '8980c988edc2c78cc43ccb718c06efd5' == MD5collect(s[0:2])
+ assert '53fd88c84ff8a285eb6e0a687e55b8c7' == MD5collect(s)
+
+ def test_MD5signature(self):
+ """Test generating a signature"""
+ s = MD5signature('111')
+ assert '698d51a19d8a121ce581499d7b701668' == s, s
+
+ s = MD5signature('222')
+ assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s
+
if __name__ == "__main__":
- suite = unittest.makeSuite(UtilTestCase, 'test_')
+ suite = unittest.TestSuite()
+ tclasses = [ dictifyTestCase,
+ MD5TestCase,
+ UtilTestCase,
+ ]
+ for tclass in tclasses:
+ names = unittest.getTestCaseNames(tclass, 'test_')
+ suite.addTests(map(tclass, names))
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
that we can still try to import the real module name and fall back to
our compatibility module if we get an ImportError. The import_as()
function defined below loads the module as the "real" name (without the
-underscore), after which all of the "import {module}" statements in the
+'_scons'), after which all of the "import {module}" statements in the
rest of our code will find our pre-loaded compatibility module.
"""
import builtins
+try:
+ import hashlib
+except ImportError:
+ # Pre-2.5 Python has no hashlib module.
+ try:
+ import_as('_scons_hashlib', 'hashlib')
+ except ImportError:
+ # If we failed importing our compatibility module, it probably
+ # means this version of Python has no md5 module. Don't do
+ # anything and let the higher layer discover this fact, so it
+ # can fall back to using timestamp.
+ pass
+
try:
set
except NameError:
# Pre-2.3 Python has no optparse module.
import_as('_scons_optparse', 'optparse')
+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
+
try:
import subprocess
except ImportError:
--- /dev/null
+#
+# __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.
+#
+
+__doc__ = """
+hashlib backwards-compatibility module for older (pre-2.5) Python versions
+
+This does not not NOT (repeat, *NOT*) provide complete hashlib
+functionality. It only wraps the portions of MD5 functionality used
+by SCons, in an interface that looks like hashlib (or enough for our
+purposes, anyway). In fact, this module will raise an ImportError if
+the underlying md5 module isn't available.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import md5
+import string
+
+class md5obj:
+
+ md5_module = md5
+
+ def __init__(self, name, string=''):
+ if not name in ('MD5', 'md5'):
+ raise ValueError, "unsupported hash type"
+ self.name = 'md5'
+ self.m = self.md5_module.md5()
+
+ def __repr__(self):
+ return '<%s HASH object @ %#x>' % (self.name, id(self))
+
+ def copy(self):
+ import copy
+ result = copy.copy(self)
+ result.m = self.m.copy()
+ return result
+
+ def digest(self):
+ return self.m.digest()
+
+ def update(self, arg):
+ return self.m.update(arg)
+
+ if hasattr(md5.md5(), 'hexdigest'):
+
+ def hexdigest(self):
+ return self.m.hexdigest()
+
+ else:
+
+ # Objects created by the underlying md5 module have no native
+ # hexdigest() method (*cough* 1.5.2 *cough*), so provide an
+ # equivalent lifted from elsewhere.
+ def hexdigest(self):
+ h = string.hexdigits
+ r = ''
+ for c in self.digest():
+ i = ord(c)
+ r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
+ return r
+
+new = md5obj
+
+def md5(string=''):
+ return md5obj('md5', string)
if mswindows:
- import threading
+ try:
+ import threading
+ except ImportError:
+ # SCons: the threading module is only used by the communicate()
+ # method, which we don't actually use, so don't worry if we
+ # can't import it.
+ pass
import msvcrt
if 0: # <-- change this to use pywin32 instead of the _subprocess driver
import pywintypes
Print_Flags = Flagger()
Verbose = 0
Readable = 0
-Raw = 0
def default_mapper(entry, name):
try:
val = None
return str(val)
+def map_action(entry, name):
+ try:
+ bact = entry.bact
+ bactsig = entry.bactsig
+ except AttributeError:
+ return None
+ return '%s [%s]' % (bactsig, bact)
+
def map_timestamp(entry, name):
try:
timestamp = entry.timestamp
return None
result = []
for i in xrange(len(bkids)):
- result.append("%s: %s" % (bkids[i], bkidsigs[i]))
+ result.append(nodeinfo_string(bkids[i], bkidsigs[i], " "))
if result == []:
return None
return string.join(result, "\n ")
map_field = {
+ 'action' : map_action,
'timestamp' : map_timestamp,
'bkids' : map_bkids,
}
return val
def nodeinfo_raw(name, ninfo, prefix=""):
- # This does essentially what the pprint module does,
- # except that it sorts the keys for deterministic output.
+ # This just formats the dictionary, which we would normally use str()
+ # to do, except that we want the keys sorted for deterministic output.
d = ninfo.__dict__
- keys = d.keys()
- keys.sort()
+ try:
+ keys = ninfo.field_list + ['_version_id']
+ except AttributeError:
+ keys = d.keys()
+ keys.sort()
l = []
for k in keys:
- l.append('%s: %s' % (repr(k), repr(d[k])))
+ l.append('%s: %s' % (repr(k), repr(d.get(k))))
return name + ': {' + string.join(l, ', ') + '}'
-def nodeinfo_string(name, ninfo, prefix=""):
- fieldlist = ["bsig", "csig", "timestamp", "size"]
+def nodeinfo_cooked(name, ninfo, prefix=""):
+ try:
+ field_list = ninfo.field_list
+ except AttributeError:
+ field_list = []
f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v)
- outlist = [name+":"] + filter(None, map(f, fieldlist))
+ outlist = [name+':'] + filter(None, map(f, field_list))
if Verbose:
- sep = "\n " + prefix
+ sep = '\n ' + prefix
else:
- sep = " "
+ sep = ' '
return string.join(outlist, sep)
-def printfield(name, entry, prefix=""):
- if Raw:
- print nodeinfo_raw(name, entry.ninfo, prefix)
- else:
- print nodeinfo_string(name, entry.ninfo, prefix)
+nodeinfo_string = nodeinfo_cooked
+def printfield(name, entry, prefix=""):
outlist = field("implicit", entry, 0)
if outlist:
if Verbose:
print " implicit:"
print " " + outlist
+ outact = field("action", entry, 0)
+ if outact:
+ if Verbose:
+ print " action: " + outact
+ else:
+ print " " + outact
-def printentries(entries):
+def printentries(entries, location):
if Print_Entries:
for name in Print_Entries:
try:
entry = entries[name]
except KeyError:
- sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, args[0]))
+ sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, location))
else:
- printfield(name, entry)
+ try:
+ ninfo = entry.ninfo
+ except AttributeError:
+ print name + ":"
+ else:
+ print nodeinfo_string(name, entry.ninfo)
+ printfield(name, entry.binfo)
else:
names = entries.keys()
names.sort()
for name in names:
- printfield(name, entries[name])
+ entry = entries[name]
+ try:
+ ninfo = entry.ninfo
+ except AttributeError:
+ print name + ":"
+ else:
+ print nodeinfo_string(name, entry.ninfo)
+ printfield(name, entry.binfo)
class Do_SConsignDB:
def __init__(self, dbm_name, dbm):
print_e = e
sys.stderr.write("sconsign: %s\n" % (print_e))
return
- except:
+ except KeyboardInterrupt:
+ raise
+ except cPickle.UnpicklingError:
sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n" % (self.dbm_name, fname))
return
+ except Exception, e:
+ sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n" % (self.dbm_name, fname, e))
+ return
if Print_Directories:
for dir in Print_Directories:
def printentries(self, dir, val):
print '=== ' + dir + ':'
- printentries(cPickle.loads(val))
+ printentries(cPickle.loads(val), dir)
def Do_SConsignDir(name):
try:
return
try:
sconsign = SCons.SConsign.Dir(fp)
- except:
- sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % name)
+ except KeyboardInterrupt:
+ raise
+ except cPickle.UnpicklingError:
+ sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % (name))
+ return
+ except Exception, e:
+ sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e))
return
- printentries(sconsign.entries)
+ printentries(sconsign.entries, args[0])
##############################################################################
helpstr = """\
Usage: sconsign [OPTIONS] FILE [...]
Options:
- -b, --bsig Print build signature information.
+ -a, --act, --action Print build action information.
-c, --csig Print content signature information.
-d DIR, --dir=DIR Print only info about DIR.
-e ENTRY, --entry=ENTRY Print only info about ENTRY.
-v, --verbose Verbose, describe each field.
"""
-opts, args = getopt.getopt(sys.argv[1:], "bcd:e:f:hirstv",
- ['bsig', 'csig', 'dir=', 'entry=',
+opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv",
+ ['act', 'action',
+ 'csig', 'dir=', 'entry=',
'format=', 'help', 'implicit',
'raw', 'readable',
'size', 'timestamp', 'verbose'])
for o, a in opts:
- if o in ('-b', '--bsig'):
- Print_Flags['bsig'] = 1
+ if o in ('-a', '--act', '--action'):
+ Print_Flags['action'] = 1
elif o in ('-c', '--csig'):
Print_Flags['csig'] = 1
elif o in ('-d', '--dir'):
elif o in ('-i', '--implicit'):
Print_Flags['implicit'] = 1
elif o in ('--raw',):
- Raw = 1
+ nodeinfo_string = nodeinfo_raw
elif o in ('-r', '--readable'):
Readable = 1
elif o in ('-s', '--size'):
"SCons.Platform",
"SCons.Scanner",
"SCons.Script",
- "SCons.Sig",
"SCons.Tool",
"SCons.Tool.packaging"],
'package_dir' : {'' : 'engine'},
packaged by Gary Oberbrunner.
"""
+import string
+
import TestSCons
+_python_ = TestSCons._python_
test = TestSCons.TestSCons()
+test.write('strip.py', """\
+import string
+import sys
+print "strip.py: %s" % string.join(sys.argv[1:])
+""")
+
test.write('SConstruct', """\
env = Environment()
myprog = env.Program('test1.c',
LIBPATH = ['.'],
- LIBS = ['mytest'])
+ LIBS = ['mytest'],
+ OBJSUFFIX = '.obj',
+ PROGSUFFIX = '.exe')
if ARGUMENTS['case']=='2':
- AddPostAction(myprog, Action('strip ' + myprog[0].abspath))
-""")
+ AddPostAction(myprog, Action(r'%(_python_)s strip.py ' + myprog[0].abspath))
+""" % locals())
test.write('test1.c', """\
extern void test_lib_fn();
test.run(arguments="-Q -c case=1")
-test.must_not_exist('test1.o')
+test.must_not_exist('test1.obj')
test.run(arguments="-Q case=2", stderr=None)
+expect = 'strip.py: %s' % test.workpath('test1.exe')
+test.fail_test(string.find(test.stdout(), expect) == -1)
+
test.pass_test()
-"""SCons.Sig.TimeStamp
-
-The TimeStamp signature package for the SCons software construction
-utility.
-
-"""
-
+#!/usr/bin/env python
#
# __COPYRIGHT__
#
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-def current(new, old):
- """Return whether a new timestamp is up-to-date with
- respect to an old timestamp.
- """
- return not old is None and new <= old
+"""
+Verify that setting exitstatfunc on an Action works as advertised.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+def always_succeed(s):
+ # Always return 0, which indicates success.
+ return 0
+
+def copy_fail(target, source, env):
+ content = open(str(source[0]), 'rb').read()
+ open(str(target[0]), 'wb').write(content)
+ return 2
+
+a = Action(copy_fail, exitstatfunc=always_succeed)
+Alias('test1', Command('test1.out', 'test1.in', a))
-def collect(signatures):
- """
- Collect a list of timestamps, returning
- the most-recent timestamp from the list
+def fail(target, source, env):
+ return 2
- signatures - a list of timestamps
- returns - the most recent timestamp
- """
+t2 = Command('test2.out', 'test2.in', Copy('$TARGET', '$SOURCE'))
+AddPostAction(t2, Action(fail, exitstatfunc=always_succeed))
+Alias('test2', t2)
+""")
- if len(signatures) == 0:
- return 0
- elif len(signatures) == 1:
- return signatures[0]
- else:
- return max(signatures)
+test.write('test1.in', "test1.in\n")
+test.write('test2.in', "test2.in\n")
-def signature(obj):
- """Generate a timestamp.
- """
- return obj.get_timestamp()
+test.run(arguments = 'test1')
-def to_string(signature):
- """Convert a timestamp to a string"""
- return str(signature)
+test.must_match('test1.out', "test1.in\n")
-def from_string(string):
- """Convert a string to a timestamp"""
- try:
- return int(string)
- except ValueError:
- # if the signature isn't an int, then
- # the user probably just switched from
- # MD5 signatures to timestamp signatures,
- # so ignore the error:
- return None
+test.run(arguments = 'test2')
+test.must_match('test2.out', "test2.in\n")
+test.pass_test()
--- /dev/null
+#!/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 changing an Action causes rebuilds when using timestamp
+signatures.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env.Decider('timestamp-match')
+env.Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.not_up_to_date(arguments = 'file.out')
+
+test.write('SConstruct', """\
+def my_copy(target, source, env):
+ open(str(target[0]), 'w').write(open(str(source[0]), 'r').read())
+env = Environment()
+env.Decider('timestamp-match')
+env.Command('file.out', 'file.in', my_copy)
+""")
+
+test.not_up_to_date(arguments = 'file.out')
+
+test.pass_test()
test.write('SConstruct', """
def foo(self):
- return 'foo-' + env['FOO']
+ return 'foo-' + self['FOO']
AddMethod(Environment, foo)
env = Environment(FOO = '111')
print env.foo()
env.AddMethod(foo, 'bar')
-print env.bar()
+env['FOO'] = '333'
+
+e = env.Clone()
+e['FOO'] = '444'
+print env.bar()
+print e.bar()
""")
expect = """\
foo-111
foo-222
-foo-222
+foo-333
+foo-444
"""
test.run(arguments = '-Q -q', stdout = expect)
--- /dev/null
+#!/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 sys
+import TestSCons
+import TestCmd
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons(match=TestCmd.match_re)
+
+test.subdir('sub1', 'sub2')
+
+test.write('build.py', r"""
+import sys
+open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
+sys.exit(0)
+""")
+
+test.write('SConstruct', """
+B = Builder(action = r'%(_python_)s build.py $TARGET $SOURCES')
+env = Environment()
+env['BUILDERS']['B'] = B
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env.B(target = 'f3.out', source = 'f3.in')
+SConscript('sub1/SConscript', "env")
+SConscript('sub2/SConscript', "env")
+
+foo = Alias('foo')
+foo2 = env.Alias('foo', ['f2.out', 'sub1'])
+assert foo == foo2
+bar = Alias('bar', ['sub2', 'f3.out'])
+env.Alias('blat', ['sub2', 'f3.out'])
+env.Alias('blat', ['f2.out', 'sub1'])
+env.Depends('f1.out', 'bar')
+
+Alias('a1', 'a1-file.in')
+Depends(Alias('a2'), 'a1')
+env.B('a2-file.out', 'a2-file.in')
+Depends('a2-file.out', 'a2')
+""" % locals())
+
+test.write(['sub1', 'SConscript'], """
+Import("env")
+env.B(target = 'f4.out', source = 'f4.in')
+env.B(target = 'f5.out', source = 'f5.in')
+env.B(target = 'f6.out', source = 'f6.in')
+""")
+
+test.write(['sub2', 'SConscript'], """
+Import("env")
+env.B(target = 'f7.out', source = 'f7.in')
+env.B(target = 'f8.out', source = 'f8.in')
+env.B(target = 'f9.out', source = 'f9.in')
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.write(['sub1', 'f4.in'], "sub1/f4.in\n")
+test.write(['sub1', 'f5.in'], "sub1/f5.in\n")
+test.write(['sub1', 'f6.in'], "sub1/f6.in\n")
+
+test.write(['sub2', 'f7.in'], "sub2/f7.in\n")
+test.write(['sub2', 'f8.in'], "sub2/f8.in\n")
+test.write(['sub2', 'f9.in'], "sub2/f9.in\n")
+
+
+
+test.run(arguments = 'foo')
+
+test.must_not_exist(test.workpath('f1.out'))
+test.must_exist(test.workpath('f2.out'))
+test.must_not_exist(test.workpath('f3.out'))
+
+test.must_exist(test.workpath('sub1', 'f4.out'))
+test.must_exist(test.workpath('sub1', 'f5.out'))
+test.must_exist(test.workpath('sub1', 'f6.out'))
+
+test.must_not_exist(test.workpath('sub2', 'f7.out'))
+test.must_not_exist(test.workpath('sub2', 'f8.out'))
+test.must_not_exist(test.workpath('sub2', 'f9.out'))
+
+test.up_to_date(arguments = 'foo')
+
+test.write(['sub1', 'f5.in'], "sub1/f5.in 2\n")
+
+test.not_up_to_date(arguments = 'foo')
+
+
+
+test.run(arguments = 'f1.out')
+
+test.must_exist(test.workpath('f1.out'))
+test.must_exist(test.workpath('f3.out'))
+
+test.up_to_date(arguments = 'f1.out')
+
+
+
+os.unlink(test.workpath('f2.out'))
+os.unlink(test.workpath('f3.out'))
+
+test.run(arguments = 'blat')
+
+test.must_exist(test.workpath('f2.out'))
+test.must_exist(test.workpath('f3.out'))
+
+test.write('f3.in', "f3.in 2 \n")
+
+expect = """.* build.py f3.out f3.in
+.* build.py f1.out f1.in
+"""
+
+test.run(arguments = '-Q f1.out', stdout = expect)
+
+test.up_to_date(arguments = 'f1.out')
+
+
+
+test.write('a1-file.in', "a1-file.in\n")
+test.write('a2-file.in', "a2-file.in\n")
+
+test.run(arguments = 'a2-file.out')
+
+test.must_match(test.workpath('a2-file.out'), "a2-file.in\n")
+
+test.up_to_date(arguments = 'a2-file.out')
+
+test.write('a1-file.in', "a1-file.in 2\n")
+
+test.not_up_to_date(arguments = 'a2-file.out')
+
+
+
+test.pass_test()
# Do some Node test operations to ensure no side-effects cause failures
File('file.in').exists()
File('file.in').is_derived()
-File('file.in').is_pseudo_derived()
env.Build('file.out', 'file.in')
""")
--- /dev/null
+#!/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 ensure_suffix argument to causes us to add the suffix
+configured for the Builder even if it looks like the target already has
+a different suffix.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+
+tbuilder = Builder(action=Copy('$TARGET', '$SOURCE'),
+ suffix='.dll',
+ ensure_suffix=True)
+
+env['BUILDERS']['TBuilder'] = tbuilder
+
+env.TBuilder("aa.bb.cc.dd","aa.aa.txt")
+""")
+
+test.write('aa.aa.txt', "clean test\n")
+
+test.run(arguments = '.')
+
+test.must_match('aa.bb.cc.dd.dll', "clean test\n")
+
+test.pass_test()
--- /dev/null
+#!/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 multiple target files get retrieved from a CacheDir correctly.
+"""
+
+import os.path
+import shutil
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('cache')
+
+test.write('SConstruct', """\
+def touch(env, source, target):
+ open('foo', 'w').write("")
+ open('bar', 'w').write("")
+CacheDir(r'%s')
+env = Environment()
+env.Command(['foo', 'bar'], ['input'], touch)
+""" % (test.workpath('cache')))
+
+test.write('input', "multiple/input\n")
+
+test.run()
+
+test.must_exist(test.workpath('foo'))
+test.must_exist(test.workpath('bar'))
+
+test.run(arguments = '-c')
+
+test.must_not_exist(test.workpath('foo'))
+test.must_not_exist(test.workpath('bar'))
+
+test.run()
+
+test.must_exist(test.workpath('foo'))
+test.must_exist(test.workpath('bar'))
+
+test.pass_test()
--- /dev/null
+#!/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 the case (reported by Jeff Petkau, bug #694744) where a target
+is source for another target with a scanner, which used to cause us
+to push the file to the CacheDir after the build signature had already
+been cleared (as a sign that the built file should now be rescanned).
+"""
+
+import os.path
+import shutil
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('cache')
+
+test.write('SConstruct', """\
+import SCons
+
+CacheDir(r'%s')
+
+def docopy(target,source,env):
+ data = source[0].get_contents()
+ f = open(target[0].rfile().get_abspath(), "wb")
+ f.write(data)
+ f.close()
+
+def sillyScanner(node, env, dirs):
+ print 'This is never called (unless we build file.out)'
+ return []
+
+SillyScanner = SCons.Scanner.Base(function = sillyScanner, skeys = ['.res'])
+
+env = Environment(tools=[],
+ SCANNERS = [SillyScanner],
+ BUILDERS = {})
+
+r = env.Command('file.res', 'file.ma', docopy)
+
+env.Command('file.out', r, docopy)
+
+# make r the default. Note that we don't even try to build file.out,
+# and so SillyScanner never runs. The bug is the same if we build
+# file.out, though.
+Default(r)
+""" % test.workpath('cache'))
+
+test.write('file.ma', "file.ma\n")
+
+test.run()
+
+test.must_not_exist(test.workpath('cache', 'N', 'None'))
+
+test.pass_test()
+#!/usr/bin/env python
#
# __COPYRIGHT__
#
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import unittest
-import SCons.Sig
-import sys
+"""
+Verify that CacheDir() works even when using timestamp signatures.
+"""
-class CalculatorTestCase(unittest.TestCase):
+import TestSCons
- def runTest(self):
- class MySigModule:
- pass
- calc = SCons.Sig.Calculator(MySigModule)
- assert calc.module == MySigModule
+test = TestSCons.TestSCons()
+test.write('SConstruct', """
+SourceSignatures('timestamp')
+TargetSignatures('content')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
-def suite():
- suite = unittest.TestSuite()
- suite.addTest(CalculatorTestCase())
- return suite
+test.write('file.in', "file.in\n")
-if __name__ == "__main__":
- runner = unittest.TextTestRunner()
- result = runner.run(suite())
- if not result.wasSuccessful():
- sys.exit(1)
+test.run()
+test.must_match('file.out', "file.in\n")
+
+test.pass_test()
--- /dev/null
+#!/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 don't perform Configure context actions when the
+-c or --clean options have been specified.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
+
+test.write('SConstruct', """\
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+SConscript( 'SConscript' )
+""")
+
+test.write('SConscript', """\
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+
+test.write('TestProgram.c', """\
+#include <stdio.h>
+
+int main() {
+ printf( "Hello\\n" );
+}
+""")
+
+lines = [
+ "Checking for C header file math.h... ",
+ "Checking for C header file no_std_c_header.h... "
+]
+
+unexpected = []
+
+test.run(arguments = '-c')
+
+for line in lines:
+ if string.find(test.stdout(), line) != -1:
+ unexpected.append(line)
+
+if unexpected:
+ print "Unexpected lines in standard output:"
+ print string.join(unexpected, '\n')
+ print "STDOUT ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+test.run(arguments = '--clean')
+
+for line in lines:
+ if string.find(test.stdout(), line) != -1:
+ unexpected.append(line)
+
+if unexpected:
+ print "Unexpected lines in standard output:"
+ print string.join(unexpected, '\n')
+ print "STDOUT ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+test.pass_test()
--- /dev/null
+#!/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 don't perform Configure context actions when the
+-H, -h or --help options have been specified.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
+
+test.write('SConstruct', """\
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
+env = conf.Finish()
+Export( 'env' )
+SConscript( 'SConscript' )
+""")
+
+test.write('SConscript', """\
+Import( 'env' )
+env.Program( 'TestProgram', 'TestProgram.c' )
+""")
+
+test.write('TestProgram.c', """\
+#include <stdio.h>
+
+int main() {
+ printf( "Hello\\n" );
+}
+""")
+
+lines = [
+ "Checking for C header file math.h... ",
+ "Checking for C header file no_std_c_header.h... "
+]
+
+unexpected = []
+
+test.run(arguments = '-H')
+
+for line in lines:
+ if string.find(test.stdout(), line) != -1:
+ unexpected.append(line)
+
+if unexpected:
+ print "Unexpected lines in standard output:"
+ print string.join(unexpected, '\n')
+ print "STDOUT ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+test.run(arguments = '-h')
+
+for line in lines:
+ if string.find(test.stdout(), line) != -1:
+ unexpected.append(line)
+
+if unexpected:
+ print "Unexpected lines in standard output:"
+ print string.join(unexpected, '\n')
+ print "STDOUT ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+test.run(arguments = '--help')
+
+for line in lines:
+ if string.find(test.stdout(), line) != -1:
+ unexpected.append(line)
+
+if unexpected:
+ print "Unexpected lines in standard output:"
+ print string.join(unexpected, '\n')
+ print "STDOUT ============================================================"
+ print test.stdout()
+ test.fail_test()
+
+test.pass_test()
test.subdir('d5')
test.write(['d5', 'f12.in'], "f12.in\n")
+d4_f10_in = os.path.join('d4', 'f10.in')
+d4_f11_out = os.path.join('d4', 'f11.out')
+d4_f12_out = os.path.join('d4', 'f12.out')
+d5_f12_in = os.path.join('d5', 'f12.in')
+
expect = test.wrap_stdout(read_str = """\
Copy("f1.out", "f1.in")
Copy("d2.out", "d2.in")
Copy("f4.out", "f4.in")
Copy("d5.out", "d5.in")
Copy("d6.out", "f6.in")
-Copy file(s): "f10.in" to "d4/f10.in"
-Copy file(s): "f11.in" to "d4/f11.out"
-Copy file(s): "d5/f12.in" to "d4/f12.out"
+Copy file(s): "f10.in" to "%(d4_f10_in)s"
+Copy file(s): "f11.in" to "%(d4_f11_out)s"
+Copy file(s): "%(d5_f12_in)s" to "%(d4_f12_out)s"
Copy("f7.out", "f7.in")
cat(["f8.out"], ["f8.in"])
cat(["f9.out"], ["f9.in"])
Copy("f9.out-Copy", "f9.in")
-""")
+""" % locals())
test.run(options = '-n', arguments = '.', stdout = expect)
test.must_not_exist('f1.out')
--- /dev/null
+#!/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 an up-to-date Decider method through a construction
+environment.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+env = Environment()
+env.Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+ return os.path.exists('has-changed')
+env.Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
--- /dev/null
+#!/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 behavior of the MD5-timestamp Decider() setting.
+"""
+
+import os
+import stat
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+m = Environment()
+m.Decider('MD5-timestamp')
+m.Command('content1.out', 'content1.in', Copy('$TARGET', '$SOURCE'))
+m.Command('content2.out', 'content2.in', Copy('$TARGET', '$SOURCE'))
+m.Command('content3.out', 'content3.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('content1.in', "content1.in 1\n")
+test.write('content2.in', "content2.in 1\n")
+test.write('content3.in', "content3.in 1\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+
+
+test.sleep()
+
+test.write('content1.in', "content1.in 2\n")
+
+test.touch('content2.in')
+
+time_content = os.stat('content3.in')[stat.ST_MTIME]
+test.write('content3.in', "content3.in 2\n")
+test.touch('content3.in', time_content)
+
+# We should only see content1.out rebuilt. The timestamp of content2.in
+# has changed, but its content hasn't, so the follow-on content check says
+# to not rebuild it. The content of content3.in has changed, but that's
+# masked by the fact that its timestamp is the same as the last run.
+
+expect = test.wrap_stdout("""\
+Copy("content1.out", "content1.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+test.up_to_date(arguments = '.')
+
+
+
+test.pass_test()
--- /dev/null
+#!/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 an up-to-date Decider method on a Node.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+file_in = File('file.in')
+file_out = File('file.out')
+Command(file_out, file_in, Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+ return os.path.exists('has-changed')
+file_in.Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
--- /dev/null
+#!/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 a default up-to-date Decider method.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+def my_decider(dependency, target, prev_ni):
+ return os.path.exists('has-changed')
+Decider(my_decider)
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+test.write('has-changed', "\n")
+
+test.not_up_to_date(arguments = '.')
+
+test.not_up_to_date(arguments = '.')
+
+test.unlink('has-changed')
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
--- /dev/null
+#!/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 an up-to-date Decider method through a construction
+environment.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+import os.path
+denv = Environment()
+env = Environment()
+n1_in = File('n1.in')
+n2_in = File('n2.in')
+n3_in = File('n3.in')
+Command( 'ccc.out', 'ccc.in', Copy('$TARGET', '$SOURCE'))
+Command( 'n1.out', n1_in, Copy('$TARGET', '$SOURCE'))
+denv.Command('ddd.out', 'ddd.in', Copy('$TARGET', '$SOURCE'))
+denv.Command('n2.out', n2_in, Copy('$TARGET', '$SOURCE'))
+env.Command( 'eee.out', 'eee.in', Copy('$TARGET', '$SOURCE'))
+env.Command( 'n3.out', n3_in, Copy('$TARGET', '$SOURCE'))
+def default_decider(dependency, target, prev_ni):
+ return os.path.exists('default-has-changed')
+def env_decider(dependency, target, prev_ni):
+ return os.path.exists('env-has-changed')
+def node_decider(dependency, target, prev_ni):
+ return os.path.exists('node-has-changed')
+Decider(default_decider)
+env.Decider(env_decider)
+n1_in.Decider(node_decider)
+n2_in.Decider(node_decider)
+n3_in.Decider(node_decider)
+""")
+
+test.write('ccc.in', "ccc.in\n")
+test.write('ddd.in', "ddd.in\n")
+test.write('eee.in', "eee.in\n")
+test.write('n1.in', "n1.in\n")
+test.write('n2.in', "n2.in\n")
+test.write('n3.in', "n3.in\n")
+
+
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+
+
+test.write('env-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'eee.out')
+test.up_to_date(arguments = 'ccc.out ddd.out n1.out n2.out n3.out')
+
+test.not_up_to_date(arguments = 'eee.out')
+test.up_to_date(arguments = 'ccc.out ddd.out n1.out n2.out n3.out')
+
+test.unlink('env-has-changed')
+
+
+
+test.write('default-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'ccc.out ddd.out')
+test.up_to_date(arguments = 'eee.out n1.out n2.out n3.out')
+
+test.not_up_to_date(arguments = 'ccc.out ddd.out')
+test.up_to_date(arguments = 'eee.out n1.out n2.out n3.out')
+
+test.unlink('default-has-changed')
+
+
+
+test.up_to_date(arguments = '.')
+
+test.write('node-has-changed', "\n")
+
+test.not_up_to_date(arguments = 'n1.out n2.out n3.out')
+test.up_to_date(arguments = 'ccc.out ddd.out eee.out')
+
+test.not_up_to_date(arguments = 'n1.out n2.out n3.out')
+test.up_to_date(arguments = 'ccc.out ddd.out eee.out')
+
+test.unlink('node-has-changed')
+
+
+
+test.pass_test()
--- /dev/null
+#!/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 various interactions of the timestamp-match and timestamp-newer
+Decider() settings:.
+"""
+
+import os
+import stat
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+m = Environment()
+m.Decider('timestamp-match')
+m.Command('match1.out', 'match1.in', Copy('$TARGET', '$SOURCE'))
+m.Command('match2.out', 'match2.in', Copy('$TARGET', '$SOURCE'))
+n = Environment()
+n.Decider('timestamp-newer')
+n.Command('newer1.out', 'newer1.in', Copy('$TARGET', '$SOURCE'))
+n.Command('newer2.out', 'newer2.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('match1.in', "match1.in\n")
+test.write('match2.in', "match2.in\n")
+test.write('newer1.in', "newer1.in\n")
+test.write('newer2.in', "newer2.in\n")
+
+test.run(arguments = '.')
+
+test.up_to_date(arguments = '.')
+
+time_match = os.stat('match2.out')[stat.ST_MTIME]
+time_newer = os.stat('newer2.out')[stat.ST_MTIME]
+
+
+
+# Now make all the source files newer than (different timestamps from)
+# the last time the targets were built, and touch the target files
+# of match1.out and newer1.out to see the different effects.
+
+test.sleep()
+
+test.touch('match1.in')
+test.touch('newer1.in')
+test.touch('match2.in')
+test.touch('newer2.in')
+
+test.sleep()
+
+test.touch('match1.out')
+test.touch('newer1.out')
+
+# We should see both match1.out and match2.out rebuilt, because the
+# source file timestamps do not match the last time they were built,
+# but only newer2.out rebuilt. newer1.out is *not* rebuilt because
+# the actual target file timestamp is, in fact, newer than the
+# source file (newer1.in) timestamp.
+
+expect = test.wrap_stdout("""\
+Copy("match1.out", "match1.in")
+Copy("match2.out", "match2.in")
+Copy("newer2.out", "newer2.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+# Now, for the somewhat pathological case, reset the match2.out and
+# newer2.out timestamps to the older timestamp when the targets were
+# first built. This will cause newer2.out to be rebuilt, because
+# the newer1.in timestamp is now newer than the older, reset target
+# file timestamp, but match2.out is *not* rebuilt because its source
+# file (match2.in) timestamp still exactly matches the timestamp
+# recorded when the target file was last built.
+
+test.touch('match2.out', time_match)
+test.touch('newer2.out', time_newer)
+
+expect = test.wrap_stdout("""\
+Copy("newer2.out", "newer2.in")
+""")
+
+test.run(arguments = '.', stdout=expect)
+
+
+
+test.pass_test()
--- /dev/null
+#!/usr/bin/env python\r
+#\r
+# __COPYRIGHT__\r
+#\r
+# Permission is hereby granted, free of charge, to any person obtaining\r
+# a copy of this software and associated documentation files (the\r
+# "Software"), to deal in the Software without restriction, including\r
+# without limitation the rights to use, copy, modify, merge, publish,\r
+# distribute, sublicense, and/or sell copies of the Software, and to\r
+# permit persons to whom the Software is furnished to do so, subject to\r
+# the following conditions:\r
+#\r
+# The above copyright notice and this permission notice shall be included\r
+# in all copies or substantial portions of the Software.\r
+#\r
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY\r
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\r
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+#\r
+\r
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"\r
+\r
+"""\r
+Verify the error when the Decider() function is handed an unknown\r
+function string.\r
+"""\r
+\r
+import TestSCons\r
+\r
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)\r
+\r
+test.write('SConstruct', """\\r
+Decider('fiddle-dee-dee')\r
+""")\r
+\r
+expect = r"""\r
+scons: \*\*\* Unknown Decider value 'fiddle-dee-dee'\r
+""" + TestSCons.file_expr\r
+\r
+test.run(arguments = '.', status = 2, stderr = expect)\r
+\r
+test.pass_test()\r
env.Command('f9.out', 'f9.in', [Cat,
Delete("Delete-$SOURCE"),
Delete("$TARGET-Delete")])
-env.Command('f10-nonexistent.out', 'f10.in', [Delete("$TARGET"),
- Cat])
-env.Command('d11-nonexistent.out', 'd11.in', [Delete("$TARGET"),
- Mkdir("$TARGET")])
-env.Command('f12-nonexistent.out', 'f12.in', [Delete("$TARGET", must_exist=0),
- Cat])
-env.Command('d13-nonexistent.out', 'd13.in', [Delete("$TARGET", must_exist=0),
- Mkdir("$TARGET")])
+
+env.Command('f10-nonexistent.out', 'f10.in',
+ [Delete("$TARGET"), Cat])
+
+env.Command(Dir('d11-nonexistent.out'), 'd11.in',
+ [Delete("$TARGET"), Mkdir("$TARGET")])
+
+env.Command('f12-nonexistent.out', 'f12.in',
+ [Delete("$TARGET", must_exist=0), Cat])
+
+env.Command(Dir('d13-nonexistent.out'), 'd13.in',
+ [Delete("$TARGET", must_exist=0), Mkdir("$TARGET")])
""")
test.write('f1', "f1\n")
env.Depends(file1, [[file2, 'file3']])
""")
+test.write('file2', "file2\n")
+test.write('file3', "file3\n")
+
test.up_to_date(arguments = '.')
test.pass_test()
test.write('junk.txt', 'junk.txt 2\n')
test.not_up_to_date(arguments='bsig.out')
-# XXX For some reason, 'csig' is still reported as up to date.
-# XXX Comment out this test until someone can look at it.
-#test.not_up_to_date(arguments='csig.out')
+test.not_up_to_date(arguments='csig.out')
test.pass_test()
test = TestSCons.TestSCons()
-install = test.workpath('install')
-install_file = test.workpath('install', 'file')
-work_file = test.workpath('work', 'file')
+work_file_out = test.workpath('work', 'file.out')
test.subdir('install', 'work')
test.write(['work', 'SConstruct'], """\
-Alias("install", Install(r"%(install)s", File('file')))
+file_out = Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+Alias("install", file_out)
# Make a directory where we expect the File() to be. This causes an
# IOError or OSError when we try to open it to read its signature.
import os
-os.mkdir(r'%(work_file)s')
+os.mkdir('file.in')
""" % locals())
if sys.platform == 'win32':
error_message = "Is a directory"
expect = """\
-scons: *** [%(install_file)s] %(work_file)s: %(error_message)s
+scons: *** [install] %(work_file_out)s: %(error_message)s
""" % locals()
test.run(chdir = 'work',
test.write(f5_txt, "f5.txt\n")
test.write(f6_txt, "f6.txt\n")
-test.run(chdir = 'work', arguments = '--debug=stacktrace .')
+test.run(chdir = 'work', arguments = '.')
-test.fail_test(test.read(f1_out) != "f1.in\n")
-test.fail_test(test.read(f2_out) != "f2.in\n")
-test.fail_test(test.read(f3_out) != "f3.in\n")
-test.fail_test(test.read(f4_out) != "sub/f4.in\n")
-test.fail_test(test.read(['work', 'f5.txt']) != "f5.txt\n")
-test.fail_test(test.read(['work', 'export', 'f5.txt']) != "f5.txt\n")
-test.fail_test(test.read(['work', 'f6.txt']) != "f6.txt\n")
-test.fail_test(test.read(['work', 'export', 'f6.txt']) != "f6.txt\n")
+test.must_match(f1_out, "f1.in\n")
+test.must_match(f2_out, "f2.in\n")
+test.must_match(f3_out, "f3.in\n")
+test.must_match(f4_out, "sub/f4.in\n")
+test.must_match(['work', 'f5.txt'], "f5.txt\n")
+test.must_match(['work', 'export', 'f5.txt'], "f5.txt\n")
+test.must_match(['work', 'f6.txt'], "f6.txt\n")
+test.must_match(['work', 'export', 'f6.txt'], "f6.txt\n")
-test.fail_test(test.read(['work', 'my_install.out']) != os.path.join('export', 'f3.out'))
+test.must_match(['work', 'my_install.out'], os.path.join('export', 'f3.out'))
# make sure the programs didn't get rebuilt, because nothing changed:
oldtime1 = os.path.getmtime(f1_out)
# Verify that we didn't link to the Installed file.
open(f2_out, 'wb').write("xyzzy\n")
-test.fail_test(test.read(['work', 'f2.out']) != "f2.in\n")
+test.must_match(['work', 'f2.out'], "f2.in\n")
# Verify that scons prints an error message
# if a target can not be unlinked before building it:
-"""SCons.Sig
-
-The Signature package for the scons software construction utility.
-
-"""
-
+#!/usr/bin/env python
#
# __COPYRIGHT__
#
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-try:
- import MD5
- default_module = MD5
-except ImportError:
- import TimeStamp
- default_module = TimeStamp
+"""
+Verify that we can still call Install() and InstallAs() even when
+no Tool modules have been loaded.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('iii')
+
+test.write('SConstruct', """
+env = Environment(tools = [])
+env.Install('iii', 'foo.in')
+env.InstallAs('foo.out', 'foo.in')
+""")
-class Calculator:
- """
- Encapsulates signature calculations and .sconsign file generating
- for the build engine.
- """
+test.write('foo.in', "foo.in\n")
- def __init__(self, module=default_module):
- """
- Initialize the calculator.
+test.run(arguments = '.')
- module - the signature module to use for signature calculations
- """
- self.module = module
+test.must_match(['iii', 'foo.in'], "foo.in\n")
+test.must_match('foo.out', "foo.in\n")
-default_calc = Calculator()
+test.pass_test()
--- /dev/null
+#!/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 $JAVABOOTCLASSPATH sets the -bootclasspath option
+on javac compilations.
+"""
+
+import os
+import string
+
+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('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")
+
+test.write('SConstruct', """
+env = Environment(tools = ['javac', 'javah'],
+ JAVAC = r'%(where_javac)s',
+ JAVABOOTCLASSPATH = ['dir1', 'dir2'])
+j1 = env.Java(target = 'class', source = 'com/Example1.java')
+j2 = env.Java(target = 'class', source = 'com/Example2.java')
+""" % locals())
+
+test.subdir('com')
+
+test.write(['com', 'Example1.java'], """\
+package com;
+
+public class Example1
+{
+
+ public static void main(String[] args)
+ {
+
+ }
+
+}
+""")
+
+test.write(['com', 'Example2.java'], """\
+package com;
+
+public class Example2
+{
+
+ public static void main(String[] args)
+ {
+
+ }
+
+}
+""")
+
+# Setting -bootclasspath messes with the Java runtime environment, so
+# we'll just take the easy way out and examine the -n output to see if
+# the expected option shows up on the command line.
+
+bootclasspath = string.join(['dir1', 'dir2'], os.pathsep)
+
+expect = """\
+%(where_javac)s -bootclasspath %(bootclasspath)s -d class -sourcepath com com/Example1.java
+%(where_javac)s -bootclasspath %(bootclasspath)s -d class -sourcepath com com/Example2.java
+""" % locals()
+
+test.run(arguments = '-Q -n .', stdout = expect)
+
+test.pass_test()
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")
+
swig = test.where_is('swig')
if not swig:
test.skip_test('Can not find installed "swig", skipping test.\n')
test.write(['SConstruct'], """\
import os,sys
-env=Environment(tools = ['default', 'javac', 'javah'])
+env=Environment(tools = ['default', 'javac', 'javah'],
+ JAVAC = r'%(where_javac)s',
+ JAVAH = r'%(where_javah)s')
Export('env')
env.PrependENVPath('PATH',os.environ.get('PATH',[]))
env['INCPREFIX']='-I'
'buildout/HelloApplet/SConscript',
'buildout/jni/SConscript',
'buildout/javah/SConscript'])
-""")
+""" % locals())
test.write(['src', 'HelloApplet', 'Hello.html'], """\
<HTML>
test.must_exist(['buildout', 'jni', 'Sample.java'])
test.must_exist(['buildout', 'jni', 'SampleJNI.class'])
test.must_exist(['buildout', 'jni', 'SampleJNI.java'])
-test.must_exist(['buildout', 'jni', 'SampleTest.class'])
test.must_exist(['buildout', 'jni', 'SampleTest.java'])
+# Some combinations of Java + SWIG apparently don't actually generate
+# a SampleTest.class file, while others do. Only issue a warning if
+# it doesn't exist.
+p = test.workpath('buildout', 'jni', 'SampleTest.class')
+import os.path
+if not os.path.exists(p):
+ print 'Warning: %s does not exist' % p
+
test.up_to_date(arguments = '.')
test.pass_test()
test = TestSCons.TestSCons()
ENV = test.java_ENV()
+
if test.detect_tool('javac', ENV=ENV):
where_javac = test.detect('JAVAC', 'javac', ENV=ENV)
else:
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:
test.write(['SConstruct'], """\
import os
-env = Environment(ENV = os.environ)
+env = Environment(ENV = os.environ,
+ JAVAC = r'%(where_javac)s',
+ JAVAH = r'%(where_javah)s')
env.Append(CPPFLAGS = ' -g -Wall')
SConscript('#foo/SConscript')
SConscript('#java/SConscript')
-""")
+""" % locals())
test.write(['foo', 'SConscript'], """\
Import('env')
foopack_jar = env.Jar(target = 'foopack.jar', source = 'classes')
""")
-test.run(arguments = '.')
+# Disable looking at stderr because some combinations of SWIG/gcc
+# generate a warning about the sWIG_JavaThrowException() function
+# being defined but not used.
+test.run(arguments = '.', stderr=None)
#test.must_exist(['java', 'classes', 'foopack', 'foopack.class'])
#test.must_exist(['java', 'classes', 'foopack', 'foopackJNI.class'])
#include <stdio.h>
#include "message1.h"
-int DisplayMessage2 (void)
+void DisplayMessage2 (void)
{
DisplayMessage1();
printf ("src/component2/hello.c\\n");
when using MinGW.
"""
+import sys
import TestSCons
_python_ = TestSCons._python_
test = TestSCons.TestSCons()
-
+if sys.platform in ('irix6',):
+ test.skip_test("Skipping mingw test on non-Windows %s platform."%sys.platform)
test.write('myrc.py', """
import sys
the displayed string when rc is called.
"""
+import sys
import TestSCons
_python_ = TestSCons._python_
test = TestSCons.TestSCons()
-
+if sys.platform in ('irix6',):
+ test.skip_test("Skipping mingw test on non-Windows %s platform."%sys.platform)
test.write('myrc.py', """
import sys
# However, this should *not* occur during a dryrun (-n). When not
# performed during a dryrun, this should not affect buildability.
# 2) Calling is_derived() should not affect buildability.
-# 3) Calling is_pseudo_derived() may cause the sbuilder to be set, and
-# it may caues the builder to be set as well, but it should not
-# adversely affect buildability.
import sys
import TestSCons
if %(_E)s:
import os
derived = map(lambda N: N.is_derived(), Nodes)
- p_derived = map(lambda N: N.is_pseudo_derived(), Nodes)
real1 = map(lambda N: os.path.exists(str(N)), Nodes)
exists = map(lambda N: N.exists(), Nodes)
real2 = map(lambda N: os.path.exists(str(N)), Nodes)
- for N,D,P,R,E,F in map(None, Nodes, derived, p_derived,
- real1, exists, real2):
- print '%%s: %%s %%s %%s %%s %%s'%%(N,D,P,R,E,F)
+ for N,D,R,E,F in map(None, Nodes, derived, real1, exists, real2):
+ print '%%s: %%s %%s %%s %%s'%%(N,D,R,E,F)
foo.SharedLibrary(target = 'foo', source = 'foo%(_obj)s')
bar.SharedLibrary(target = 'bar', source = 'bar%(_obj)s')
via_node = node.exists() # side effect causes copy from src
after = os.path.exists(str(node))
node.is_derived()
- node.is_pseudo_derived()
import SCons.Script
if GetOption('no_exec'):
if (before,via_node,after) != (False,False,False):
--- /dev/null
+#!/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, when a file is specified multiple times in a target
+list, we still build all of the necessary targets.
+
+This used to cause a target to "disappear" from the DAG when its reference
+count dropped below 0, because we were subtracting the duplicated target
+mutiple times even though we'd only visit it once. This was particularly
+hard to track down because the DAG itself (when printed with --tree=prune,
+for example) doesn't show the duplication in the *target* list.
+"""
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.subdir('work')
+
+tar_output = test.workpath('work.tar')
+
+test.write(['work', 'copy.py'], """\
+import sys
+import time
+time.sleep(int(sys.argv[1]))
+open(sys.argv[2], 'wb').write(open(sys.argv[3], 'rb').read())
+""")
+
+test.write(['work', 'SConstruct'], """\
+env = Environment()
+out1 = File('f1.out')
+out2 = File('f2.out')
+env.Command([out1, out1], 'f1.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE')
+env.Command([out2, out2], 'f2.in', r'%(_python_)s copy.py 3 $TARGET $SOURCE')
+
+env.Tar(r'%(tar_output)s', Dir('.'))
+""" % locals())
+
+test.write(['work', 'f1.in'], "work/f1.in\n")
+test.write(['work', 'f2.in'], "work/f2.in\n")
+
+test.run(chdir = 'work', arguments = tar_output + ' -j2')
+
+test.must_match(['work', 'f1.out'], "work/f1.in\n")
+test.must_match(['work', 'f2.out'], "work/f2.in\n")
+test.must_exist(tar_output)
+
+test.pass_test()
--- /dev/null
+#!/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 substition of the $TARGET string in progress output, including
+overwriting it by setting the overwrite= keyword argument.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(universal_newlines=None)
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('$TARGET\\r', overwrite=True)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+S1.in\r \rS1.out\rCopy("S1.out", "S1.in")
+ \rS2.in\r \rS2.out\rCopy("S2.out", "S2.in")
+ \rS3.in\r \rS3.out\rCopy("S3.out", "S3.in")
+ \rS4.in\r \rS4.out\rCopy("S4.out", "S4.in")
+ \rSConstruct\r \r.\r"""
+
+if os.linesep != '\n':
+ expect = string.replace(expect, '\n', os.linesep)
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
--- /dev/null
+#!/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 expected behavior of just telling a Progress() object to print
+a dot for every visited Node.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('.')
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+..Copy("S1.out", "S1.in")
+..Copy("S2.out", "S2.in")
+..Copy("S3.out", "S3.in")
+..Copy("S4.out", "S4.in")
+.."""
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
--- /dev/null
+#!/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 file= argument to Progress() allows us to redirect the
+progress output.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('stderr: $TARGET\\n', file=open('progress.out', 'w'))
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+test.run(arguments = '-Q .', stdout=expect)
+
+expect = """\
+stderr: S1.in
+stderr: S1.out
+stderr: S2.in
+stderr: S2.out
+stderr: S3.in
+stderr: S3.out
+stderr: S4.in
+stderr: S4.out
+stderr: SConstruct
+stderr: .
+"""
+
+if os.linesep != '\n':
+ expect = string.replace(expect, '\n', os.linesep)
+
+test.must_match('progress.out', expect)
+
+test.pass_test()
--- /dev/null
+#!/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 passing our own function to Progress().
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+def my_progress_function(node, *args, **kw):
+ sys.stderr.write('mpf: %s\\n' % node)
+Progress(my_progress_function)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+mpf: S1.in
+mpf: S1.out
+mpf: S2.in
+mpf: S2.out
+mpf: S3.in
+mpf: S3.out
+mpf: S4.in
+mpf: S4.out
+mpf: SConstruct
+mpf: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
--- /dev/null
+#!/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 "interval=" parameter to Progress skips Nodes.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress('stderr: $TARGET\\n', file=sys.stderr, interval=2)
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+stderr: S1.out
+stderr: S2.out
+stderr: S3.out
+stderr: S4.out
+stderr: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
--- /dev/null
+#!/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 passing a callable object to Progress().
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import sys
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+class my_progress:
+ count = 0
+ def __call__(self, node, *args, **kw):
+ self.count = self.count + 1
+ sys.stderr.write('%s: %s\\n' % (self.count, node))
+Progress(my_progress())
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect_stdout = """\
+Copy("S1.out", "S1.in")
+Copy("S2.out", "S2.in")
+Copy("S3.out", "S3.in")
+Copy("S4.out", "S4.in")
+"""
+
+expect_stderr = """\
+1: S1.in
+2: S1.out
+3: S2.in
+4: S2.out
+5: S3.in
+6: S3.out
+7: S4.in
+8: S4.out
+9: SConstruct
+10: .
+"""
+
+test.run(arguments = '-Q .', stdout=expect_stdout, stderr=expect_stderr)
+
+test.pass_test()
--- /dev/null
+#!/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 output when a Progress() call is initialized with the list
+that represents a canonical "spinner" on the output.
+"""
+
+import os
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons(universal_newlines=None)
+
+test.write('SConstruct', r"""
+env = Environment()
+env['BUILDERS']['C'] = Builder(action=Copy('$TARGET', '$SOURCE'))
+Progress(['-\r', '\\\r', '|\r', '/\r'])
+env.C('S1.out', 'S1.in')
+env.C('S2.out', 'S2.in')
+env.C('S3.out', 'S3.in')
+env.C('S4.out', 'S4.in')
+""")
+
+test.write('S1.in', "S1.in\n")
+test.write('S2.in', "S2.in\n")
+test.write('S3.in', "S3.in\n")
+test.write('S4.in', "S4.in\n")
+
+expect = """\
+\\\r|\rCopy("S1.out", "S1.in")
+/\r-\rCopy("S2.out", "S2.in")
+\\\r|\rCopy("S3.out", "S3.in")
+/\r-\rCopy("S4.out", "S4.in")
+\\\r|\r"""
+
+if os.linesep != '\n':
+ expect = string.replace(expect, '\n', os.linesep)
+
+test.run(arguments = '-Q .', stdout=expect)
+
+test.pass_test()
# If so, then print whatever it showed us (which is in and of itself
# an indication that it built correctly) but don't fail the test.
expect = 'cannot connect to X server'
- test.fail_test(test.stdout() != '' or
- string.find(test.stderr(), expect) == -1 or \
- (test.status>>8) != 1)
-
+ test.fail_test(test.stdout())
+ test.fail_test(string.find(test.stderr(), expect) == -1)
+ if test.status != 1 and (test.status>>8) != 1:
+ sys.stdout.write('test_realqt returned status %s\n' % test.status)
+ test.fail_test()
QTDIR = os.environ['QTDIR']
PATH = os.environ['PATH']
test.run(arguments = "build_dir=1 chdir=1 dup=0 " +
test.workpath('build_dup0', aaa_exe) )
-test.must_exist(['build_dup0', moc],
- ['build_dup0', aaa_exe])
+test.must_exist(['build_dup0', moc])
test.pass_test()
--- /dev/null
+#!/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 Return() function stops processing the SConscript file
+at the point is called, unless the stop= keyword argument is supplied.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+SConscript('SConscript1')
+x = SConscript('SConscript2')
+y, z = SConscript('SConscript3')
+a4, b4 = SConscript('SConscript4')
+print "x =", x
+print "y =", y
+print "z =", z
+print "a4 =", a4
+print "b4 =", b4
+""")
+
+test.write('SConscript1', """\
+print "line 1"
+Return()
+print "line 2"
+""")
+
+test.write('SConscript2', """\
+print "line 3"
+x = 7
+Return('x')
+print "line 4"
+""")
+
+test.write('SConscript3', """\
+print "line 5"
+y = 8
+z = 9
+Return('y z')
+print "line 6"
+""")
+
+test.write('SConscript4', """\
+a4 = 'aaa'
+b4 = 'bbb'
+print "line 7"
+Return('a4', 'b4', stop=False)
+b4 = 'b-after'
+print "line 8"
+""")
+
+expect = """\
+line 1
+line 3
+line 5
+line 7
+line 8
+x = 7
+y = 8
+z = 9
+a4 = aaa
+b4 = bbb
+"""
+
+test.run(arguments = '-q -Q', stdout=expect)
+
+test.pass_test()
test = TestSCons.TestSCons()
+swig = test.where_is('swig')
+
+if not swig:
+ test.skip_test('Can not find installed "swig", skipping test.\n')
+
# swig-python expects specific filenames.
# the platform specific suffix won't necessarily work.
if sys.platform == 'win32':
%extend
{
const char* __str__() { return "linalg.Vector()"; }
- int __len__() { return $self->size(); }
- double __getitem__(int key) { return $self->operator[](key); }
- void __setitem__(int key, double value) { $self->operator[](key) = value; }
%pythoncode %{
def __iter__(self):
test.run(stderr=TestSCons.noisy_ar,
match=TestSCons.match_re_dotall)
-# XXX Note that the generated .h files still get scanned twice,
-# once before they're generated and once after. That's the
-# next thing to fix here.
+# Note that the generated .h files still get scanned twice,
+# but that's really once each as a child of libg_1.o and libg_2.o.
test.must_match("MyCScan.out", """\
libg_1.c: 1
libg_2.c: 1
libg_3.c: 1
-libg_gx.h: 1
+libg_gx.h: 2
libg_gy.h: 1
libg_gz.h: 1
-libg_w.h: 1
+libg_w.h: 2
""")
test.pass_test()
--- /dev/null
+#!/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 generate the proper warning, but don't die, when someone
+tries to import the SCons.Sig module (which no longer exists) and
+use the things we used to define therein.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+SConstruct = test.workpath('SConstruct')
+
+test.write(SConstruct, """
+import SCons.Sig
+x = SCons.Sig.default_calc
+x = SCons.Sig.default_module
+x = SCons.Sig.MD5.current()
+x = SCons.Sig.MD5.collect()
+x = SCons.Sig.MD5.signature()
+x = SCons.Sig.MD5.to_string()
+x = SCons.Sig.MD5.from_string()
+x = SCons.Sig.TimeStamp.current()
+x = SCons.Sig.TimeStamp.collect()
+x = SCons.Sig.TimeStamp.signature()
+x = SCons.Sig.TimeStamp.to_string()
+x = SCons.Sig.TimeStamp.from_string()
+""")
+
+expect = """
+scons: warning: The SCons.Sig module no longer exists.
+ Remove the following "import SCons.Sig" line to eliminate this warning:
+""" + test.python_file_line(SConstruct, 2)
+
+test.run(arguments = '.', stderr=expect)
+
+test.pass_test()
env.SourceCode('$SUBDIR', Builder(action=sc_cat, env=env))
env.Cat('aaa.out', 'sub/aaa.in')
bbb_in = File('sub/bbb.in')
-bbb_in.is_pseudo_derived()
+bbb_in.is_derived()
env.Cat('bbb.out', bbb_in)
env.Cat('ccc.out', 'sub/ccc.in')
env.Cat('all', ['aaa.out', 'bbb.out', 'ccc.out'])
+++ /dev/null
-#!/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 imp
-import os
-import os.path
-import time
-
-import TestSCons
-
-test = TestSCons.TestSCons()
-
-test.write('SConstruct', """
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-
-SourceSignatures('timestamp')
-""")
-
-test.write('f1.in', "f1.in\n")
-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 f2.out f3.out f4.out',
- stdout = test.wrap_stdout("""\
-scons: `f1.out' is up to date.
-build(["f2.out"], ["f2.in"])
-scons: `f3.out' is up to date.
-build(["f4.out"], ["f4.in"])
-"""))
-
-os.utime(test.workpath('f1.in'),
- (os.path.getatime(test.workpath('f1.in')),
- os.path.getmtime(test.workpath('f1.in'))+10))
-os.utime(test.workpath('f3.in'),
- (os.path.getatime(test.workpath('f3.in')),
- os.path.getmtime(test.workpath('f3.in'))+10))
-
-test.run(arguments = 'f1.out f2.out f3.out f4.out',
- stdout = test.wrap_stdout("""\
-build(["f1.out"], ["f1.in"])
-scons: `f2.out' is up to date.
-build(["f3.out"], ["f3.in"])
-scons: `f4.out' is up to date.
-"""))
-
-test.write('SConstruct', """
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-
-SourceSignatures('MD5')
-""")
-
-test.write('f1.in', "f1.in\n")
-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 f2.out f3.out f4.out',
- stdout = test.wrap_stdout("""\
-scons: `f1.out' is up to date.
-build(["f2.out"], ["f2.in"])
-scons: `f3.out' is up to date.
-build(["f4.out"], ["f4.in"])
-"""))
-
-os.utime(test.workpath('f1.in'),
- (os.path.getatime(test.workpath('f1.in')),
- os.path.getmtime(test.workpath('f1.in'))+10))
-os.utime(test.workpath('f3.in'),
- (os.path.getatime(test.workpath('f3.in')),
- os.path.getmtime(test.workpath('f3.in'))+10))
-
-test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
-
-test.write('SConstruct', """
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'f1.out', source = 'f1.in')
-env.B(target = 'f2.out', source = 'f2.in')
-env.B(target = 'f3.out', source = 'f3.in')
-env.B(target = 'f4.out', source = 'f4.in')
-""")
-
-test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
-
-test.write('SConstruct', """
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env2 = env.Clone()
-env2.SourceSignatures('MD5')
-env.B(target = 'f5.out', source = 'f5.in')
-env.B(target = 'f6.out', source = 'f6.in')
-env2.B(target = 'f7.out', source = 'f7.in')
-env2.B(target = 'f8.out', source = 'f8.in')
-
-SourceSignatures('timestamp')
-""")
-
-test.write('f5.in', "f5.in\n")
-test.write('f6.in', "f6.in\n")
-test.write('f7.in', "f7.in\n")
-test.write('f8.in', "f8.in\n")
-
-test.run(arguments = 'f5.out f7.out')
-
-test.run(arguments = 'f5.out f6.out f7.out f8.out',
- stdout = test.wrap_stdout("""\
-scons: `f5.out' is up to date.
-build(["f6.out"], ["f6.in"])
-scons: `f7.out' is up to date.
-build(["f8.out"], ["f8.in"])
-"""))
-
-os.utime(test.workpath('f5.in'),
- (os.path.getatime(test.workpath('f5.in')),
- os.path.getmtime(test.workpath('f5.in'))+10))
-os.utime(test.workpath('f7.in'),
- (os.path.getatime(test.workpath('f7.in')),
- os.path.getmtime(test.workpath('f7.in'))+10))
-
-test.run(arguments = 'f5.out f6.out f7.out f8.out',
- stdout = test.wrap_stdout("""\
-build(["f5.out"], ["f5.in"])
-scons: `f6.out' is up to date.
-scons: `f7.out' is up to date.
-scons: `f8.out' is up to date.
-"""))
-
-test.up_to_date(arguments = 'f5.out f6.out f7.out f8.out')
-
-# Ensure that switching signature types causes a rebuild:
-test.write('SConstruct', """
-SourceSignatures('MD5')
-
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.write('switch.in', "switch.in\n")
-
-switch_out_switch_in = test.wrap_stdout('build(["switch.out"], ["switch.in"])\n')
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('SConstruct', """
-SourceSignatures('timestamp')
-
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('SConstruct', """
-SourceSignatures('MD5')
-
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'switch.out', source = 'switch.in')
-""")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-test.up_to_date(arguments = 'switch.out')
-
-test.write('switch.in', "switch.in 2\n")
-
-test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
-
-
-# Test both implicit_cache and timestamp signatures at the same time:
-test.write('SConstruct', """
-SetOption('implicit_cache', 1)
-SourceSignatures('timestamp')
-
-def build(env, target, source):
- open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
-B = Builder(action = build)
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'both.out', source = 'both.in')
-""")
-
-test.write('both.in', "both.in 1\n")
-
-both_out_both_in = test.wrap_stdout('build(["both.out"], ["both.in"])\n')
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 2\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 3\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.write('both.in', "both.in 4\n")
-
-test.run(arguments = 'both.out', stdout = both_out_both_in)
-
-time.sleep(2)
-
-test.up_to_date(arguments = 'both.out')
-
-test.pass_test()
--- /dev/null
+#!/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 os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+
+base_sconstruct_contents = """\
+def build(env, target, source):
+ open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env.B(target = 'f3.out', source = 'f3.in')
+env.B(target = 'f4.out', source = 'f4.in')
+"""
+
+def write_SConstruct(test, sigtype):
+ contents = base_sconstruct_contents
+ if sigtype:
+ contents = contents + ("\nSourceSignatures('%s')\n" % sigtype)
+ test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, 'timestamp')
+
+test.write('f1.in', "f1.in\n")
+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 f2.out f3.out f4.out',
+ stdout = test.wrap_stdout("""\
+scons: `f1.out' is up to date.
+build(["f2.out"], ["f2.in"])
+scons: `f3.out' is up to date.
+build(["f4.out"], ["f4.in"])
+"""))
+
+
+
+os.utime(test.workpath('f1.in'),
+ (os.path.getatime(test.workpath('f1.in')),
+ os.path.getmtime(test.workpath('f1.in'))+10))
+os.utime(test.workpath('f3.in'),
+ (os.path.getatime(test.workpath('f3.in')),
+ os.path.getmtime(test.workpath('f3.in'))+10))
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+ stdout = test.wrap_stdout("""\
+build(["f1.out"], ["f1.in"])
+scons: `f2.out' is up to date.
+build(["f3.out"], ["f3.in"])
+scons: `f4.out' is up to date.
+"""))
+
+
+
+# Switching to content signatures from timestamps should rebuild,
+# because we didn't record the content signatures last time.
+
+write_SConstruct(test, 'MD5')
+
+test.not_up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.sleep()
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.touch('f1.in', os.path.getmtime(test.workpath('f1.in'))+10)
+test.touch('f3.in', os.path.getmtime(test.workpath('f3.in'))+10)
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+write_SConstruct(test, None)
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.pass_test()
--- /dev/null
+#!/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 use of env.SourceSignatures() correctly overrides the
+default behavior.
+"""
+
+import os
+import os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+base_sconstruct_contents = """\
+def build(env, target, source):
+ open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env2 = env.Copy()
+env2.SourceSignatures('%s')
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+env2.B(target = 'f3.out', source = 'f3.in')
+env2.B(target = 'f4.out', source = 'f4.in')
+
+SourceSignatures('%s')
+"""
+
+def write_SConstruct(test, env_sigtype, default_sigtype):
+ contents = base_sconstruct_contents % (env_sigtype, default_sigtype)
+ test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, 'MD5', 'timestamp')
+
+test.write('f1.in', "f1.in\n")
+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 f2.out f3.out f4.out',
+ stdout = test.wrap_stdout("""\
+scons: `f1.out' is up to date.
+build(["f2.out"], ["f2.in"])
+scons: `f3.out' is up to date.
+build(["f4.out"], ["f4.in"])
+"""))
+
+
+
+test.sleep()
+
+test.touch('f1.in')
+test.touch('f3.in')
+
+test.run(arguments = 'f1.out f2.out f3.out f4.out',
+ stdout = test.wrap_stdout("""\
+build(["f1.out"], ["f1.in"])
+scons: `f2.out' is up to date.
+scons: `f3.out' is up to date.
+scons: `f4.out' is up to date.
+"""))
+
+test.up_to_date(arguments = 'f1.out f2.out f3.out f4.out')
+
+
+
+test.pass_test()
--- /dev/null
+#!/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 the simultaneous use of implicit_cache and
+SourceSignatures('timestamp')
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+SetOption('implicit_cache', 1)
+SourceSignatures('timestamp')
+
+def build(env, target, source):
+ open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'both.out', source = 'both.in')
+""")
+
+both_out_both_in = test.wrap_stdout('build(["both.out"], ["both.in"])\n')
+
+
+
+test.write('both.in', "both.in 1\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 2\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 3\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.write('both.in', "both.in 4\n")
+
+test.run(arguments = 'both.out', stdout = both_out_both_in)
+
+
+
+test.sleep(2)
+
+test.up_to_date(arguments = 'both.out')
+
+
+
+test.pass_test()
--- /dev/null
+
+#!/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 os.path
+
+import TestSConsign
+
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+
+
+test.write('SConstruct', """\
+def build(env, target, source):
+ open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'f1.out', source = 'f1.in')
+env.B(target = 'f2.out', source = 'f2.in')
+SourceSignatures('timestamp')
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+
+test.run(arguments = '.')
+
+
+
+expect = r"""=== .:
+SConstruct: None \d+ \d+
+f1.in: None \d+ \d+
+f1.out: \S+ \d+ \d+
+ f1.in: None \d+ \d+
+ \S+ \[build\(target, source, env\)\]
+f2.in: None \d+ \d+
+f2.out: \S+ \d+ \d+
+ f2.in: None \d+ \d+
+ \S+ \[build\(target, source, env\)\]
+"""
+
+test.run_sconsign(arguments = test.workpath('.sconsign'),
+ stdout = expect)
+
+
+
+test.pass_test()
--- /dev/null
+#!/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__"
+
+"""
+Make sure that SourceSignatures() works when overrides are used on a
+Builder call. (Previous implementations used methods that would stay
+bound to the underlying construction environment, which in this case
+meant ignoring the 'timestamp' setting and still using the underlying
+content signature.)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+DefaultEnvironment().SourceSignatures('MD5')
+env = Environment()
+env.SourceSignatures('timestamp')
+env.Command('foo.out', 'foo.in', Copy('$TARGET', '$SOURCE'), FOO=1)
+""")
+
+test.write('foo.in', "foo.in 1\n")
+
+test.run(arguments = 'foo.out')
+
+test.sleep()
+
+test.write('foo.in', "foo.in 1\n")
+
+test.not_up_to_date(arguments = 'foo.out')
+
+test.pass_test()
--- /dev/null
+#!/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 switching SourceSignature() types no longer causes rebuilds.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+base_sconstruct_contents = """\
+SourceSignatures('%s')
+
+def build(env, target, source):
+ open(str(target[0]), 'wt').write(open(str(source[0]), 'rt').read())
+B = Builder(action = build)
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'switch.out', source = 'switch.in')
+"""
+
+def write_SConstruct(test, sig_type):
+ contents = base_sconstruct_contents % sig_type
+ test.write('SConstruct', contents)
+
+
+write_SConstruct(test, 'MD5')
+
+test.write('switch.in', "switch.in\n")
+
+switch_out_switch_in = test.wrap_stdout('build(["switch.out"], ["switch.in"])\n')
+
+test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
+
+test.up_to_date(arguments = 'switch.out')
+
+
+
+write_SConstruct(test, 'timestamp')
+
+test.up_to_date(arguments = 'switch.out')
+
+
+
+write_SConstruct(test, 'MD5')
+
+test.not_up_to_date(arguments = 'switch.out')
+
+
+
+test.write('switch.in', "switch.in 2\n")
+
+test.run(arguments = 'switch.out', stdout = switch_out_switch_in)
+
+
+
+test.pass_test()
import sys
test.write(['build', 'docs', 'test.normalized.1.pdf'], pdf_output_1)
test.write(['build', 'docs', 'test.normalized.2.pdf'], pdf_output_2)
- sys.stdout.write("***** 1 and 2 are different!\n")
+ sys.stdout.write("***** 1.pdf and 2.pdf are different!\n")
sys.stdout.write(test.diff_substr(pdf_output_1, pdf_output_2, 80, 80) + '\n')
sys.stdout.write("Output from run 1:\n")
sys.stdout.write(test.stdout(-1) + '\n')
sys.stdout.flush()
test.fail_test()
-assert ps_output_1 == ps_output_2, test.diff_substr(ps_output_1, ps_output_2, 80, 80)
+if ps_output_1 != ps_output_2:
+ import sys
+ sys.stdout.write("***** 1.ps and 2.ps are different!\n")
+ sys.stdout.write(test.diff_substr(ps_output_1, ps_output_2, 80, 80) + '\n')
+ sys.stdout.write("Output from run 1:\n")
+ sys.stdout.write(test.stdout(-1) + '\n')
+ sys.stdout.write("Output from run 2:\n")
+ sys.stdout.write(test.stdout() + '\n')
+ sys.stdout.flush()
+ test.fail_test()
--- /dev/null
+#!/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__"
+
+"""
+Check that all auxilary files created by LaTeX are properly cleaned by scons -c.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+latex = test.where_is('latex')
+
+if not latex:
+ test.skip_test("Could not find tex or latex; skipping test(s).\n")
+
+# package hyperref generates foo.out
+# package comment generates comment.cut
+# todo: add makeindex etc.
+input_file = r"""
+\documentclass{article}
+\usepackage{hyperref}
+\usepackage{comment}
+\specialcomment{foocom}{}{}
+\begin{document}
+\begin{foocom}
+Hi
+\end{foocom}
+As stated in \cite{X}, this is a bug-a-boo.
+\bibliography{fooref}
+\bibliographystyle{plain}
+\end{document}
+"""
+
+bibfile = r"""
+@Article{X,
+ author = "Mr. X",
+ title = "A determination of bug-a-boo-ness",
+ journal = "Journal of B.a.B.",
+ year = 1920,
+ volume = 62,
+ pages = 291
+}
+"""
+
+test.write('SConstruct', """\
+DVI( "foo.ltx" )
+""")
+
+test.write('foo.ltx', input_file)
+test.write('fooref.bib', bibfile)
+
+test.run()
+
+test.must_exist('foo.log')
+test.must_exist('foo.aux')
+test.must_exist('foo.bbl')
+test.must_exist('foo.blg')
+test.must_exist('comment.cut')
+test.must_exist('foo.out')
+
+test.run(arguments = '-c')
+
+test.must_not_exist('foo.log')
+test.must_not_exist('foo.aux')
+test.must_not_exist('foo.bbl')
+test.must_not_exist('foo.blg')
+test.must_not_exist('comment.cut')
+test.must_not_exist('foo.out')
+
+test.pass_test()
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Verify basic interaction of the historic TargetSignatures('build')
+and TargetSignatures('content') settings, overriding one with
+the other in specific construction environments.
+"""
+
import TestSCons
test = TestSCons.TestSCons()
-test.write('SConstruct', """
+
+
+sconstruct_contents = """\
env = Environment()
def copy1(env, source, target):
open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
def copy2(env, source, target):
+ %s
return copy1(env, source, target)
env['BUILDERS']['Copy1'] = Builder(action=copy1)
env.Copy1('foo.out', 'foo.mid')
env2 = env.Clone()
-env2.TargetSignatures('build')
+env2.TargetSignatures('%s')
env2.Copy2('bar.mid', 'bar.in')
env2.Copy1('bar.out', 'bar.mid')
-TargetSignatures('content')
-""")
+TargetSignatures('%s')
+"""
+
+def write_SConstruct(test, *args):
+ contents = sconstruct_contents % args
+ test.write('SConstruct', contents)
+
+
+
+write_SConstruct(test, '', 'build', 'content')
test.write('foo.in', 'foo.in')
test.write('bar.in', 'bar.in')
test.up_to_date(arguments='bar.out foo.out')
-test.write('SConstruct', """
-env = Environment()
-
-def copy1(env, source, target):
- open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
-
-def copy2(env, source, target):
- x = 2 # added this line
- return copy1(env, source, target)
-
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
-env2 = env.Clone()
-env2.TargetSignatures('build')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
+# Change the code in the the copy2() function, which should change
+# its content and trigger a rebuild of the targets built with it.
-TargetSignatures('content')
-""")
+write_SConstruct(test, 'x = 2 # added this line', 'build', 'content')
test.run(arguments="bar.out foo.out",
stdout=test.wrap_stdout("""\
scons: `foo.out' is up to date.
"""))
-test.write('SConstruct', """
-env = Environment()
-
-def copy1(env, source, target):
- open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
-
-def copy2(env, source, target):
- x = 2 # added this line
- return copy1(env, source, target)
-
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
-
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
-
-env2 = env.Copy()
-env2.TargetSignatures('content')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
-
-TargetSignatures('build')
-""")
-test.run(arguments="bar.out foo.out",
- stdout=test.wrap_stdout("""\
-copy1(["bar.out"], ["bar.mid"])
-copy1(["foo.out"], ["foo.mid"])
-"""))
-test.write('SConstruct', """
-env = Environment()
+# Swapping content and build signatures no longer causes a rebuild
+# because we record the right underlying information regardless.
-def copy1(env, source, target):
- open(str(target[0]), 'wb').write(open(str(source[0]), 'rb').read())
+write_SConstruct(test, 'x = 2 # added this line', 'content', 'build')
-def copy2(env, source, target):
- return copy1(env, source, target)
+test.up_to_date(arguments="bar.out foo.out")
-env['BUILDERS']['Copy1'] = Builder(action=copy1)
-env['BUILDERS']['Copy2'] = Builder(action=copy2)
-env.Copy2('foo.mid', 'foo.in')
-env.Copy1('foo.out', 'foo.mid')
-env2 = env.Copy()
-env2.TargetSignatures('content')
-env2.Copy2('bar.mid', 'bar.in')
-env2.Copy1('bar.out', 'bar.mid')
+# Change the code in the the copy2() function back again, which should
+# trigger another rebuild of the targets built with it.
-TargetSignatures('build')
-""")
+write_SConstruct(test, '', 'content', 'build')
test.run(arguments='bar.out foo.out',
stdout=test.wrap_stdout("""\
"""))
+
test.pass_test()
--- /dev/null
+#!/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 TargetSignatures('content') setting to override
+SourceSignatures('timestamp') settings.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+
+
+test.write('SConstruct', """\
+env = Environment()
+
+def copy(env, source, target):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+copyAction = Action(copy, "Copying $TARGET")
+
+SourceSignatures('timestamp')
+
+env['BUILDERS']['Copy'] = Builder(action=copyAction)
+
+env.Copy('foo.out', 'foo.in')
+
+env2 = env.Clone()
+env2.TargetSignatures('content')
+env2.Copy('bar.out', 'bar.in')
+AlwaysBuild('bar.out')
+
+env.Copy('final', ['foo.out', 'bar.out', 'extra.in'])
+env.Ignore('final', 'extra.in')
+""")
+
+test.write('foo.in', "foo.in\n")
+test.write('bar.in', "bar.in\n")
+test.write('extra.in', "extra.in 1\n")
+
+test.run()
+
+test.must_match('final', "foo.in\nbar.in\nextra.in 1\n")
+
+test.sleep()
+test.write('extra.in', "extra.in 2\n")
+
+test.run()
+
+test.must_match('final', "foo.in\nbar.in\nextra.in 1\n")
+
+
+
+test.pass_test()
--- /dev/null
+#!/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__"
+
+"""
+Make sure that TargetSignatures() works when overrides are used on a
+Builder call. Previous implementations used methods that would stay
+bound to the underlying construction environment and cause weird
+behavior like infinite recursion.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+env.TargetSignatures('content')
+env.Command('foo.out', 'foo.mid', Copy('$TARGET', '$SOURCE'), FOO=1)
+env.Command('foo.mid', 'foo.in', Copy('$TARGET', '$SOURCE'), FOO=2)
+""")
+
+test.write('foo.in', "foo.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('foo.mid', "foo.in\n")
+test.must_match('foo.out', "foo.in\n")
+
+test.pass_test()
test = TestSCons.TestSCons(match=TestCmd.match_re)
-# Run all of the tests with both types of source signature
-# to make sure there's no difference in behavior.
-for source_signature in ['MD5', 'timestamp']:
+python = TestSCons.python
- print "Testing Value node with source signatures:", source_signature
-
- test.write('SConstruct', """
+SConstruct_content = """
SourceSignatures(r'%(source_signature)s')
class Custom:
env = Environment()
env['BUILDERS']['B'] = Builder(action = create)
-env['BUILDERS']['S'] = Builder(action = '%(_python_)s put $SOURCES into $TARGET')
+env['BUILDERS']['S'] = Builder(action = r'%(_python_)s put.py $SOURCES into $TARGET')
env.B('f1.out', Value(P))
env.B('f2.out', env.Value(L))
env.B('f3.out', Value(C))
V = Value('my value')
env.B2(V, 'f3.out')
env.B3('f5.out', V)
-""" % locals())
+"""
- test.write('put', """
+test.write('put.py', """\
import os
import string
import sys
open(sys.argv[-1],'wb').write(string.join(sys.argv[1:-2]))
""")
+# Run all of the tests with both types of source signature
+# to make sure there's no difference in behavior.
+for source_signature in ['MD5', 'timestamp']:
+
+ print "Testing Value node with source signatures:", source_signature
+
+ test.write('SConstruct', SConstruct_content % locals())
+
test.run(arguments='-c')
test.run()
# becomes an issue or some refactoring restores the old behavior.
test.run(arguments = bad_drive + 'not_mentioned',
- stderr = "scons: *** No drive `%s' for target `%snot_mentioned'. Stop.\n" % (bad_drive, bad_drive),
+ stderr = "scons: *** Do not know how to make target `%snot_mentioned'. Stop.\n" % (bad_drive),
status = 2)
test.run(arguments = bad_drive + 'no_target_1',
test.run(chdir='w1',
arguments="--max-drift=0 -f SConstruct1 foo.mid",
stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
+
test.run(chdir='w1',
arguments="--max-drift=0 -f SConstruct2 foo.out",
stdout = test.wrap_stdout('build(["foo.out"], ["foo.mid"])\n'))
test.up_to_date(chdir='w1',
options="--max-drift=0 -f SConstruct1",
arguments="foo.mid")
+
test.up_to_date(chdir='w1',
options="--max-drift=0 -f SConstruct2",
arguments="foo.out")
test.sleep() # make sure foo.in rewrite has new mod-time
test.write(['w1', 'foo.in'], "foo.in 2")
-test.run(chdir='w1',
- arguments="--max-drift=0 -f SConstruct1 foo.mid",
- stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
# Because we're using --max-drift=0, we use the cached csig value
-# and think that foo.mid hasn't changed even though it has on disk.
+# and think that foo.in hasn't changed even though it has on disk.
test.up_to_date(chdir='w1',
- options="--max-drift=0 -f SConstruct2",
- arguments="foo.out")
+ options="--max-drift=0 -f SConstruct1",
+ arguments="foo.mid")
+# Now try with --max-drift disabled. The build of foo.out should still
+# be considered up-to-date, but the build of foo.mid now detects the
+# change and rebuilds, too, which then causes a rebuild of foo.out.
test.up_to_date(chdir='w1',
- options="--max-drift=0 -f SConstruct1",
- arguments="foo.mid")
-test.up_to_date(chdir='w1',
- options="--max-drift=0 -f SConstruct2",
+ options="--max-drift=-1 -f SConstruct2",
arguments="foo.out")
-# Now try with --max-drift disabled. The build of foo.mid should still
-# be considered up-to-date, but the build of foo.out now detects the
-# change and rebuilds, too.
-test.up_to_date(chdir='w1',
- options="--max-drift=-1 -f SConstruct1",
- arguments="foo.mid")
+test.run(chdir='w1',
+ arguments="--max-drift=-1 -f SConstruct1 foo.mid",
+ stdout = test.wrap_stdout('build(["foo.mid"], ["foo.in"])\n'))
+
test.run(chdir='w1',
arguments="--max-drift=-1 -f SConstruct2 foo.out",
stdout = test.wrap_stdout('build(["foo.out"], ["foo.mid"])\n'))
# Verify that exceptions caused by exit values of builder actions are
-# correectly signalled, for both Serial and Parallel jobs.
+# correctly signalled, for both Serial and Parallel jobs.
test.write('myfail.py', r"""\
import sys
test.write(SConstruct_path, """
Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
env = Environment(BUILDERS = { 'Fail' : Fail })
-env.Fail(target = 'f1', source = 'f1.in')
+env.Fail(target = 'out.f1', source = 'in.f1')
""" % locals())
-test.write('f1.in', "f1.in\n")
+test.write('in.f1', "in.f1\n")
-expected_stderr = "scons: \*\*\* \[f1\] Error 1\n"
+expected_stderr = "scons: \\*\\*\\* \\[out.f1\\] Error 1\n"
test.run(arguments = '.', status = 2, stderr = expected_stderr)
test.run(arguments = '-j2 .', status = 2, stderr = expected_stderr)
test.write(SConstruct_path, """
Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
env = Environment(BUILDERS = { 'Fail' : Fail })
-env.Fail(target = 'f1', source = 'f1.in')
-env.Fail(target = 'f2', source = 'f2.in')
-env.Fail(target = 'f3', source = 'f3.in')
+env.Fail(target = 'out.f1', source = 'in.f1')
+env.Fail(target = 'out.f2', source = 'in.f2')
+env.Fail(target = 'out.f3', source = 'in.f3')
""" % locals())
-# f2.in is not created to cause a Task.prepare exception
-test.write('f3.in', 'f3.in\n')
+# in.f2 is not created to cause a Task.prepare exception
+test.write('in.f1', 'in.f1\n')
+test.write('in.f3', 'in.f3\n')
# In Serial task mode, get the first exception and stop
test.run(arguments = '.', status = 2, stderr = expected_stderr)
# In Parallel task mode, we will get all three exceptions.
expected_stderr_list = [
- expected_stderr,
- "scons: \*\*\* Source `f2\.in' not found, needed by target `f2'\. Stop\.\n",
- string.replace(expected_stderr, 'f1', 'f3')
- ]
-
-# Unfortunately, we aren't guaranteed what order we will get the
-# exceptions in...
-orders = [ (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), (3,2,1) ]
-otexts = []
-for A,B,C in orders:
- otexts.append("%s%s%s"%(expected_stderr_list[A-1],
- expected_stderr_list[B-1],
- expected_stderr_list[C-1]))
-
-
-expected_stderrs = "(" + string.join(otexts, "|") + ")"
-
-test.run(arguments = '-j3 .', status = 2, stderr = expected_stderrs)
+ "scons: *** [out.f1] Error 1\n",
+ "scons: *** Source `in.f2' not found, needed by target `out.f2'. Stop.\n",
+ "scons: *** [out.f3] Error 1\n",
+]
+
+# To get all three exceptions simultaneously, we execute -j7 to create
+# one thread each for the SConstruct file and {in,out}.f[123]. Note that
+# it's important that the input (source) files sort earlier alphabetically
+# than the output files, so they're visited first in the dependency graph
+# walk of '.' and are already considered up-to-date when we kick off the
+# "simultaneous" builds of the output (target) files.
+
+test.run(arguments = '-j7 .', status = 2, stderr = None)
+
+missing = []
+for es in expected_stderr_list:
+ if string.find(test.stderr(), es) == -1:
+ missing.append(es)
+
+if missing:
+ sys.stderr.write("Missing the following lines from stderr:\n")
+ for m in missing:
+ sys.stderr.write(m)
+ sys.stderr.write('STDERR ===============================================\n')
+ sys.stderr.write(test.stderr())
+ test.fail_test(1)
test.pass_test()
--- /dev/null
+#!/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 call get_csig() from a function action without
+causing problems. (This messed up a lot of internal state before
+the Big Signature Refactoring.)
+
+Test case courtesy of Damyan Pepper.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+args = "--debug=explain"
+
+test.write('SConstruct', """\
+env = Environment()
+
+def action( source, target, env ):
+ target[0].get_csig()
+ f = open( str(target[0]), 'w' )
+ for s in source:
+ f.write( s.get_contents() )
+ f.close()
+
+builder = env.Builder( action=action )
+
+builder( env, target = "target.txt", source = "source.txt" )
+""")
+
+test.write("source.txt", "source.txt 1\n")
+
+test.run(arguments=args)
+
+
+
+test.write("source.txt", "source.txt 2")
+
+
+
+expect_rebuild = test.wrap_stdout("""\
+scons: rebuilding `target.txt' because `source.txt' changed
+action(["target.txt"], ["source.txt"])
+""")
+
+test.not_up_to_date(arguments=args)
+
+
+
+test.pass_test()
test.subdir('include', 'subdir', ['subdir', 'include'], 'inc2')
+# Set TargetSignatures('build') because a lot of the test below expect
+# the old behavior of non-essential changes in .h files will propagate
+# and cause the executable file to be re-linked as well (even if the
+# object file was rebuilt to the exact same contents as last time).
+
test.write('SConstruct', """
+TargetSignatures('build')
env = Environment(CPPPATH = Split('inc2 include'))
obj = env.Object(target='prog', source='subdir/prog.c')
env.Program(target='prog', source=obj)
--- /dev/null
+#!/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 $IMPLICIT_COMMAND_DEPENDENCIES variable controls
+whether or not the implicit dependency on executed commands
+is added to targets.
+"""
+
+import TestSCons
+
+python = TestSCons.python
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+generate_build_py_py_contents = """\
+#!%(python)s
+import os
+import sys
+
+open(sys.argv[1], 'w').write('''\
+#!/usr/bin/env %(python)s
+import os.path
+import string
+import sys
+fp = open(sys.argv[1], 'wb')
+args = [os.path.split(sys.argv[0])[1]] + sys.argv[1:]
+fp.write(string.join(args) + '\\\\n' + '%(extra)s')
+for infile in sys.argv[2:]:
+ fp.write(open(infile, 'rb').read())
+fp.close()
+''')
+os.chmod(sys.argv[1], 0755)
+
+"""
+
+extra = ''
+test.write('generate_build_py.py', generate_build_py_py_contents % locals())
+
+test.write('SConstruct', """
+generate = Builder(action = r'%(_python_)s $GENERATE $TARGET')
+build = Builder(action = r'$BUILD_PY $TARGET $SOURCES')
+env = Environment(BUILDERS = {
+ 'GenerateBuild' : generate,
+ 'BuildFile' : build,
+ },
+ GENERATE = 'generate_build_py.py',
+ BUILD_PY = 'build.py',
+ )
+env.PrependENVPath('PATH', '.')
+env.PrependENVPath('PATHEXT', '.PY')
+env0 = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+env1 = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = 1)
+envNone = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = None)
+envFalse = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = False)
+envTrue = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = True)
+
+build_py = env.GenerateBuild('${BUILD_PY}', [])
+AlwaysBuild(build_py)
+
+env.BuildFile('file.out', 'file.in')
+env0.BuildFile('file0.out', 'file.in')
+env1.BuildFile('file1.out', 'file.in')
+envNone.BuildFile('fileNone.out', 'file.in')
+envFalse.BuildFile('fileFalse.out', 'file.in')
+envTrue.BuildFile('fileTrue.out', 'file.in')
+""" % locals())
+
+
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--tree=all .')
+
+expect_none = 'build.py %s file.in\nfile.in\n'
+
+test.must_match('file.out', expect_none % 'file.out')
+test.must_match('file0.out', expect_none % 'file0.out')
+test.must_match('file1.out', expect_none % 'file1.out')
+test.must_match('fileNone.out', expect_none % 'fileNone.out')
+test.must_match('fileFalse.out', expect_none % 'fileFalse.out')
+test.must_match('fileTrue.out', expect_none % 'fileTrue.out')
+
+
+
+extra = 'xyzzy\\\\n'
+test.write('generate_build_py.py', generate_build_py_py_contents % locals())
+
+test.run(arguments = '--tree=all .')
+
+expect_extra = 'build.py %s file.in\nxyzzy\nfile.in\n'
+
+test.must_match('file.out', expect_extra % 'file.out')
+test.must_match('file0.out', expect_none % 'file0.out')
+test.must_match('file1.out', expect_extra % 'file1.out')
+test.must_match('fileNone.out', expect_none % 'fileNone.out')
+test.must_match('fileFalse.out', expect_none % 'fileFalse.out')
+test.must_match('fileTrue.out', expect_extra % 'fileTrue.out')
+
+
+test.pass_test()
--- /dev/null
+#!/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 expected behavior when an implicit dependency is modified
+asynchronously (that is, mid-build and without our knowledge).
+
+Test case courtesy Greg Noel.
+"""
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+import SCons.Defaults
+env = Environment()
+env['BUILDERS']['C'] = Builder(action = Copy('$TARGET', '$SOURCE'),
+ source_scanner = SCons.Defaults.CScan)
+env['BUILDERS']['Mod'] = Builder(action = r'%(_python_)s mod.py')
+Alias('seq', env.C('one.c'))
+Alias('seq', env.Mod('mod', 'mod.py'))
+Alias('seq', env.C('two.c'))
+Default('seq')
+""" % locals())
+
+test.write(['hdr.h'], """\
+/* empty header */
+""")
+
+test.write(['mod.py'], """\
+open('mod', 'w').write(open('mod.py', 'r').read())
+open('hdr.h', 'w').write("/* modified */\\n")
+""")
+
+test.write(['one.c'], """\
+#include "hdr.h"
+""")
+
+test.write(['two.c'], """\
+#include "hdr.h"
+""")
+
+# The first run builds the file 'one', then runs the 'mod' script
+# (which update modifies the 'hdr.h' file) then builds the file 'two'.
+test.run(arguments = 'seq')
+
+# The 'hdr.h' file had its original contents when 'one' was built,
+# and modified contents when 'two' was built. Because we took a
+# look at 'hdr.h' once, up front, we think both files are out of
+# date and will rebuild both (even though 'two' is really up to date).
+#
+# A future enhancement might add some sort of verification mode that
+# would examine 'hdr.h' again when 'two' was built, thereby avoiding
+# the unnecessary rebuild. In that case, the second line below
+# will need to change to "test.up_to_date(...)".
+test.not_up_to_date(arguments = 'one')
+test.not_up_to_date(arguments = 'two')
+
+# Regardless of what happened on the middle run(s), both files should
+# be up to date now.
+test.up_to_date(arguments = 'seq')
+
+test.pass_test()
f4b_in = File('dir/f4b.in')
f4b_in.exists()
f4b_in.is_derived()
-f4b_in.is_pseudo_derived()
env.Cat(target = 'dir/f4b.out', source = f4b_in)
""")
"Base.stat()",
"Dir.srcdir_list()",
"File.exists()",
- "FS._doLookup()",
"Node._children_get()",
]
test = TestSCons.TestSCons()
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
test.write('SConstruct', """
env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
env.Program('foo', Split('foo.c bar.c'))
stree = """
[E B C ]+-foo.xxx
[E B C ] +-foo.ooo
-[E ] | +-foo.c
-[E ] | +-foo.h
-[E ] | +-bar.h
+[E C ] | +-foo.c
+[E C ] | +-foo.h
+[E C ] | +-bar.h
+[E C ] | +-%(CC)s
[E B C ] +-bar.ooo
-[E ] +-bar.c
-[E ] +-bar.h
-[E ] +-foo.h
-"""
+[E C ] | +-bar.c
+[E C ] | +-bar.h
+[E C ] | +-foo.h
+[E C ] | +-%(CC)s
+[E C ] +-%(LINK)s
+""" % locals()
test.run(arguments = "--debug=stree foo.xxx")
test.fail_test(string.find(test.stdout(), stree) == -1)
[ B ]+-foo.xxx
[ B ] +-foo.ooo
-[E ] | +-foo.c
-[E ] | +-foo.h
-[E ] | +-bar.h
+[E C ] | +-foo.c
+[E C ] | +-foo.h
+[E C ] | +-bar.h
+[E C ] | +-%(CC)s
[ B ] +-bar.ooo
-[E ] +-bar.c
-[E ] +-bar.h
-[E ] +-foo.h
-"""
+[E C ] | +-bar.c
+[E C ] | +-bar.h
+[E C ] | +-foo.h
+[E C ] | +-%(CC)s
+[E C ] +-%(LINK)s
+""" % locals()
test.run(arguments = '-c foo.xxx')
+# Before anything else, make sure we get valid --debug=time results
+# when just running the help option.
+test.run(arguments = "-h --debug=time")
+
+
+
def num(s, match):
return float(re.search(match, s).group(1))
test = TestSCons.TestSCons()
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
test.write('SConstruct', """
env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
env.Program('Foo', Split('Foo.c Bar.c'))
| +-Foo.c
| +-Foo.h
| +-Bar.h
+ | +-%(CC)s
+-Bar.ooo
- +-Bar.c
- +-Bar.h
- +-Foo.h
-"""
+ | +-Bar.c
+ | +-Bar.h
+ | +-Foo.h
+ | +-%(CC)s
+ +-%(LINK)s
+""" % locals()
test.run(arguments = "--debug=tree Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
tree2 = """
+-.
+-Bar.c
+ +-Bar.h
+-Bar.ooo
| +-Bar.c
| +-Bar.h
| +-Foo.h
+ | +-%(CC)s
+-Foo.c
+ +-Foo.h
+-Foo.ooo
| +-Foo.c
| +-Foo.h
| +-Bar.h
+ | +-%(CC)s
+-Foo.xxx
| +-Foo.ooo
| | +-Foo.c
| | +-Foo.h
| | +-Bar.h
+ | | +-%(CC)s
| +-Bar.ooo
- | +-Bar.c
- | +-Bar.h
- | +-Foo.h
+ | | +-Bar.c
+ | | +-Bar.h
+ | | +-Foo.h
+ | | +-%(CC)s
+ | +-%(LINK)s
+-SConstruct
-"""
+""" % locals()
+
test.run(arguments = "--debug=tree .")
-test.fail_test(string.find(test.stdout(), tree2) == -1)
+if string.find(test.stdout(), tree2) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
# Make sure we print the debug stuff even if there's a build failure.
test.write('Bar.h', """
test.run(arguments = "--debug=tree Foo.xxx",
status = 2,
stderr = None)
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
test.pass_test()
test.write('SConstruct', """
env = Environment()
-env.Command('file.out', 'file.mid', Copy('$TARGET', '$SOURCE'))
-env.Command('file.mid', 'file.in', Copy('$TARGET', '$SOURCE'))
+
+# We name the files 'Tfile' so that they will sort after the SConstruct
+# file regardless of whether the test is being run on a case-sensitive
+# or case-insensitive system.
+
+env.Command('Tfile.out', 'Tfile.mid', Copy('$TARGET', '$SOURCE'))
+env.Command('Tfile.mid', 'Tfile.in', Copy('$TARGET', '$SOURCE'))
""")
-test.write('file.in', "file.in\n")
+test.write('Tfile.in', "Tfile.in\n")
expect_stdout = test.wrap_stdout("""\
Taskmaster: '.': children:
- ['SConstruct', 'file.in', 'file.mid', 'file.out']
- waiting on unstarted children:
- ['file.mid', 'file.out']
-Taskmaster: 'file.mid': children:
- ['file.in']
- evaluating file.mid
-Copy("file.mid", "file.in")
-Taskmaster: 'file.out': children:
- ['file.mid']
- evaluating file.out
-Copy("file.out", "file.mid")
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+ waiting on unfinished children:
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+Taskmaster: 'SConstruct': evaluating SConstruct
+Taskmaster: 'Tfile.in': evaluating Tfile.in
+Taskmaster: 'Tfile.mid': children:
+ ['Tfile.in']
+ evaluating Tfile.mid
+Copy("Tfile.mid", "Tfile.in")
+Taskmaster: 'Tfile.out': children:
+ ['Tfile.mid']
+ evaluating Tfile.out
+Copy("Tfile.out", "Tfile.mid")
Taskmaster: '.': children:
- ['SConstruct', 'file.in', 'file.mid', 'file.out']
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
evaluating .
-Taskmaster: '.': already handled (executed)
""")
test.run(arguments='--taskmastertrace=- .', stdout=expect_stdout)
expect_stdout = test.wrap_stdout("""\
-Copy("file.mid", "file.in")
-Copy("file.out", "file.mid")
+Copy("Tfile.mid", "Tfile.in")
+Copy("Tfile.out", "Tfile.mid")
""")
test.run(arguments='--taskmastertrace=trace.out .', stdout=expect_stdout)
expect_trace = """\
Taskmaster: '.': children:
- ['SConstruct', 'file.in', 'file.mid', 'file.out']
- waiting on unstarted children:
- ['file.mid', 'file.out']
-Taskmaster: 'file.mid': children:
- ['file.in']
- evaluating file.mid
-Taskmaster: 'file.out': children:
- ['file.mid']
- evaluating file.out
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+ waiting on unfinished children:
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
+Taskmaster: 'SConstruct': evaluating SConstruct
+Taskmaster: 'Tfile.in': evaluating Tfile.in
+Taskmaster: 'Tfile.mid': children:
+ ['Tfile.in']
+ evaluating Tfile.mid
+Taskmaster: 'Tfile.out': children:
+ ['Tfile.mid']
+ evaluating Tfile.out
Taskmaster: '.': children:
- ['SConstruct', 'file.in', 'file.mid', 'file.out']
+ ['SConstruct', 'Tfile.in', 'Tfile.mid', 'Tfile.out']
evaluating .
-Taskmaster: '.': already handled (executed)
"""
test.must_match('trace.out', expect_trace)
test = TestSCons.TestSCons()
+CC = test.detect('CC')
+LINK = test.detect('LINK')
+if LINK is None: LINK = CC
+
test.write('SConstruct', """
env = Environment(OBJSUFFIX = '.ooo', PROGSUFFIX = '.xxx')
env.Program('Foo', Split('Foo.c Bar.c'))
| +-Foo.c
| +-Foo.h
| +-Bar.h
+ | +-%(CC)s
+-Bar.ooo
- +-Bar.c
- +-Bar.h
- +-Foo.h
-"""
+ | +-Bar.c
+ | +-Bar.h
+ | +-Foo.h
+ | +-%(CC)s
+ +-%(LINK)s
+""" % locals()
test.run(arguments = "--tree=all Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
tree2 = """
+-.
+-Bar.c
+ +-Bar.h
+-Bar.ooo
| +-Bar.c
| +-Bar.h
| +-Foo.h
+ | +-%(CC)s
+-Foo.c
+ +-Foo.h
+-Foo.ooo
| +-Foo.c
| +-Foo.h
| +-Bar.h
+ | +-%(CC)s
+-Foo.xxx
| +-Foo.ooo
| | +-Foo.c
| | +-Foo.h
| | +-Bar.h
+ | | +-%(CC)s
| +-Bar.ooo
- | +-Bar.c
- | +-Bar.h
- | +-Foo.h
+ | | +-Bar.c
+ | | +-Bar.h
+ | | +-Foo.h
+ | | +-%(CC)s
+ | +-%(LINK)s
+-SConstruct
-"""
+""" % locals()
test.run(arguments = "--tree=all .")
-test.fail_test(string.find(test.stdout(), tree2) == -1)
+if string.find(test.stdout(), tree2) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
tree3 = """
+-.
+-Bar.c
+ +-Bar.h
+-Bar.ooo
| +-Bar.c
| +-Bar.h
| +-Foo.h
+ | +-%(CC)s
+-Foo.c
+ +-Foo.h
+-Foo.ooo
| +-Foo.c
| +-Foo.h
| +-Bar.h
+ | +-%(CC)s
+-Foo.xxx
| +-[Foo.ooo]
| +-[Bar.ooo]
+ | +-%(LINK)s
+-SConstruct
-"""
+""" % locals()
test.run(arguments = "--tree=all,prune .")
-test.fail_test(string.find(test.stdout(), tree3) == -1)
+if string.find(test.stdout(), tree3) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
test.run(arguments = "--tree=prune .")
-test.fail_test(string.find(test.stdout(), tree3) == -1)
+if string.find(test.stdout(), tree3) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
tree4 = """
E = exists
[ B ]+-Foo.xxx
[ B ] +-Foo.ooo
-[E ] | +-Foo.c
-[E ] | +-Foo.h
-[E ] | +-Bar.h
+[E C ] | +-Foo.c
+[E C ] | +-Foo.h
+[E C ] | +-Bar.h
+[E C ] | +-%(CC)s
[ B ] +-Bar.ooo
-[E ] +-Bar.c
-[E ] +-Bar.h
-[E ] +-Foo.h
-"""
+[E C ] | +-Bar.c
+[E C ] | +-Bar.h
+[E C ] | +-Foo.h
+[E C ] | +-%(CC)s
+[E C ] +-%(LINK)s
+""" % locals()
test.run(arguments = '-c Foo.xxx')
test.run(arguments = "--no-exec --tree=all,status Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree4) == -1)
+if string.find(test.stdout(), tree4) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
test.run(arguments = "--no-exec --tree=status Foo.xxx")
-test.fail_test(string.find(test.stdout(), tree4) == -1)
+if string.find(test.stdout(), tree4) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
# Make sure we print the debug stuff even if there's a build failure.
test.write('Bar.h', """
test.run(arguments = "--tree=all Foo.xxx",
status = 2,
stderr = None)
-test.fail_test(string.find(test.stdout(), tree1) == -1)
+if string.find(test.stdout(), tree1) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
test.pass_test()
--- /dev/null
+#!/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__"
+
+"""
+Make sure that --tree=derived output with a library dependency shows
+the dependency on the library. (On earlier versions of the Microsoft
+toolchain this wouldn't show up unless the library already existed
+on disk.)
+
+Issue 1363: http://scons.tigris.org/issues/show_bug.cgi?id=1363
+"""
+
+import string
+import sys
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env = Environment(LIBPREFIX='',
+ LIBSUFFIX='.lib',
+ OBJSUFFIX='.obj',
+ EXESUFFIX='.exe')
+env.AppendENVPath('PATH', '.')
+l = env.Library( 'util.lib', 'util.c' )
+p = env.Program( 'test.exe', 'main.c', LIBS=l )
+env.Command( 'foo.h', p, '$SOURCE > $TARGET')
+""")
+
+test.write('main.c', """\
+#include <stdlib.h>
+#include <stdio.h>
+int
+main(int argc, char *argv)
+{
+ printf("#define FOO_H \\"foo.h\\"\\n");
+ return (0);
+}
+""")
+
+test.write('util.c', """\
+void
+util(void)
+{
+ ;
+}
+""")
+
+expect = """
+ +-test.exe
+ +-main.obj
+ +-util.lib
+ +-util.obj
+"""
+
+test.run(arguments = '--tree=derived foo.h')
+if string.find(test.stdout(), expect) == -1:
+ sys.stdout.write('Did not find expected tree in the following output:\n')
+ sys.stdout.write(test.stdout())
+ test.fail_test()
+
+test.up_to_date(arguments = 'foo.h')
+
+test.pass_test()
--- /dev/null
+#!/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 the FindInstalledFiles() and the FindSourceFiles() functions.
+"""
+
+import os.path
+import string
+import TestSCons
+
+python = TestSCons.python
+test = TestSCons.TestSCons()
+
+test.write( "f1", "" )
+test.write( "f2", "" )
+test.write( "f3", "" )
+
+test.write( 'SConstruct', r"""
+env = Environment(tools=['default', 'packaging'])
+prog = env.Install( 'bin/', ["f1", "f2"] )
+env.File( "f3" )
+
+src_files = map(str, env.FindSourceFiles())
+oth_files = map(str, env.FindInstalledFiles())
+src_files.sort()
+oth_files.sort()
+
+print src_files
+print oth_files
+""")
+
+bin_f1 = os.path.join('bin', 'f1')
+bin_f2 = os.path.join('bin', 'f2')
+
+bin__f1 = string.replace(bin_f1, '\\', '\\\\')
+bin__f2 = string.replace(bin_f2, '\\', '\\\\')
+
+expect_read = """\
+['SConstruct', 'f1', 'f2', 'f3']
+['%(bin__f1)s', '%(bin__f2)s']
+""" % locals()
+
+expect_build = """\
+Install file: "f1" as "%(bin_f1)s"
+Install file: "f2" as "%(bin_f2)s"
+""" % locals()
+
+expected = test.wrap_stdout(read_str = expect_read, build_str = expect_build)
+
+test.run(stdout=expected)
+
+test.pass_test()
--- /dev/null
+#!/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 the ability to use a explicit target package name and the use
+of FindInstalledFiles() in conjuction with .msi packages.
+"""
+
+import os
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+try:
+ from xml.dom.minidom import *
+except ImportError:
+ test.skip_test('Canoot import xml.dom.minidom skipping test\n')
+
+wix = test.Environment().WhereIs('candle')
+
+if not wix:
+ test.skip_test("No 'candle' found; skipping test\n")
+
+#
+# build with minimal tag set and test for the given package meta-data
+#
+test.write( 'file1.exe', "file1" )
+test.write( 'file2.exe', "file2" )
+
+test.write('SConstruct', """
+import os
+
+env = Environment(tools=['default', 'packaging'])
+
+f1 = env.Install( '/usr/' , 'file1.exe' )
+f2 = env.Install( '/usr/' , 'file2.exe' )
+
+env.Alias( 'install', [ f1, f2 ] )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2',
+ PACKAGETYPE = 'msi',
+ SUMMARY = 'balalalalal',
+ DESCRIPTION = 'this should be reallly really long',
+ VENDOR = 'Nanosoft_2000',
+ source = env.FindInstalledFiles(),
+ target = "mypackage.msi",
+ )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'foo-1.2.wxs' )
+test.must_exist( 'foo-1.2.msi' )
+
+dom = parse( test.workpath( 'foo-1.2.wxs' ) )
+Product = dom.getElementsByTagName( 'Product' )[0]
+Package = dom.getElementsByTagName( 'Package' )[0]
+
+test.fail_test( not Product.attributes['Manufacturer'].value == 'Nanosoft_2000' )
+test.fail_test( not Product.attributes['Version'].value == '1.2' )
+test.fail_test( not Product.attributes['Name'].value == 'foo' )
+
+test.fail_test( not Package.attributes['Description'].value == 'balalalalal' )
+test.fail_test( not Package.attributes['Comments'].value == 'this should be reallly really long' )
+
+test.pass_test()
--- /dev/null
+#!/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__"
+
+"""
+See if the packaging tool is able to build multiple packages at once.
+
+TODO: test if the packages are clean versions (i.e. do not contain files
+ added by different packager runs)
+"""
+
+import TestSCons
+
+python = TestSCons.python
+
+test = TestSCons.TestSCons()
+
+zip = test.detect('ZIP', 'zip')
+
+if not zip:
+ test.skip_test('zip not found, skipping test\n')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE = ['src_zip', 'src_targz'],
+ target = ['src.zip', 'src.tar.gz'],
+ PACKAGEROOT = 'test',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src.zip' )
+test.must_exist( 'src.tar.gz' )
+
+test.write('SConstruct', """
+Program( 'src/main.c' )
+env=Environment(tools=['default', 'packaging'])
+env.Package( PACKAGETYPE = ['src_zip', 'src_targz'],
+ NAME = "src", VERSION = "1.0",
+ PACKAGEROOT = 'test',
+ source = [ 'src/main.c', 'SConstruct' ] )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src-1.0.zip' )
+test.must_exist( 'src-1.0.tar.gz' )
+
+test.pass_test()
--- /dev/null
+#!/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 build packages in different subdirectories.
+
+Test case courtesy Andrew Smith.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skip_test('No TAR executable found; skipping test\n')
+
+test.subdir('one', 'two', 'three')
+
+test.write('SConstruct', """\
+env = Environment(tools=['default', 'packaging'])
+Export('env')
+SConscript(dirs = ['one', 'two', 'three'])
+""")
+
+SConscript_template = """\
+Import('*')
+
+files = env.Install('/usr/bin', '%s.sh')
+
+pkg = env.Package(NAME = '%s',
+ VERSION = '1.0.0',
+ PACKAGETYPE = 'targz',
+ source = [files]
+ )
+"""
+
+test.write(['one', 'SConscript'], SConscript_template % ('one', 'one'))
+test.write(['two', 'SConscript'], SConscript_template % ('two', 'two'))
+test.write(['three', 'SConscript'], SConscript_template % ('three', 'three'))
+
+test.write(['one', 'one.sh'], "one/one.sh\n")
+test.write(['two', 'two.sh'], "two/two.sh\n")
+test.write(['three', 'three.sh'], "three/three.sh\n")
+
+test.run(arguments = '.')
+
+test.must_match(['one', 'one-1.0.0', 'usr', 'bin', 'one.sh'], "one/one.sh\n")
+test.must_match(['two', 'two-1.0.0', 'usr', 'bin', 'two.sh'], "two/two.sh\n")
+test.must_match(['three', 'three-1.0.0', 'usr', 'bin', 'three.sh'], "three/three.sh\n")
+
+test.must_exist(['one', 'one-1.0.0.tar.gz'])
+test.must_exist(['two', 'two-1.0.0.tar.gz'])
+test.must_exist(['three', 'three-1.0.0.tar.gz'])
+
+test.pass_test()
)
""" % locals())
-test.run(arguments='package PACKAGETYPE=rpm', stderr = None)
-
src_rpm = 'foo-1.2.3-0.src.rpm'
machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+test.run(arguments='package PACKAGETYPE=rpm', stderr = None)
+
test.must_exist( src_rpm )
test.must_exist( machine_rpm )
+test.must_not_exist( 'bin/main.c' )
+test.must_not_exist( '/bin/main.c' )
+
+test.run(arguments='-c package PACKAGETYPE=rpm', stderr = None)
+test.run(arguments='package --package-type=rpm', stderr = None)
+test.must_exist( src_rpm )
+test.must_exist( machine_rpm )
test.must_not_exist( 'bin/main.c' )
test.must_not_exist( '/bin/main.c' )
tar = test.detect('TAR', 'tar')
if not tar:
- test.skipt_test('tar not found, skipping test\n')
+ test.skip_test('tar not found, skipping test\n')
#
# TEST: subdir creation and file copying
""" % locals())
# first run: build the package
-# second run: test if the intermediate files have been cleaned
-test.run( arguments='' )
-test.run( arguments='-c' )
+# second run: make sure everything is up-to-date (sanity check)
+# third run: test if the intermediate files have been cleaned
+test.run( arguments='.' )
+test.up_to_date( arguments='.' )
+test.run( arguments='-c .' )
src_rpm = 'foo-1.2.3-0.src.rpm'
machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
--- /dev/null
+#!/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 the ability to create a rpm package from a explicit target name.
+"""
+
+import os
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+rpm_build_root = test.workpath('rpm_build_root')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+env.Append(RPMFLAGS = r' --buildroot %(rpm_build_root)s')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c') )
+
+env.Alias( 'install', prog )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ X_RPM_GROUP = 'Application/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog ],
+ target = "my_rpm_package.rpm",
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( machine_rpm )
+test.must_exist( src_rpm )
+test.must_not_exist( 'bin/main' )
+test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n')
+test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n')
+
+test.pass_test()
cmd = 'rpm -qp --queryformat \'%%{GROUP}-%%{SUMMARY}-%%{DESCRIPTION}\' %s'
-os.environ['LC_ALL'] = 'de_DE.utf8'
os.environ['LANGUAGE'] = 'de'
out = os.popen( cmd % test.workpath(machine_rpm) ).read()
test.fail_test( out != 'Applikation/büro-hallo-das sollte wirklich lang sein' )
-os.environ['LC_ALL'] = 'fr_FR.utf8'
os.environ['LANGUAGE'] = 'fr'
out = os.popen( cmd % test.workpath(machine_rpm) ).read()
test.fail_test( out != 'Application/bureau-bonjour-ceci devrait être vraiment long' )
--- /dev/null
+#!/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 the ability to create more than rpm file with different package root
+from one SCons environment.
+"""
+
+import os
+import TestSCons
+
+machine = TestSCons.machine
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+scons = test.program
+
+rpm = test.Environment().WhereIs('rpm')
+
+if not rpm:
+ test.skip_test('rpm not found, skipping test\n')
+
+rpm_build_root = test.workpath('rpm_build_root')
+
+test.subdir('src')
+
+test.write( [ 'src', 'main.c' ], r"""
+int main( int argc, char* argv[] )
+{
+ return 0;
+}
+""")
+
+test.write('SConstruct', """
+import os
+
+env=Environment(tools=['default', 'packaging'])
+
+env.Prepend(RPM = 'TAR_OPTIONS=--wildcards ')
+env.Append(RPMFLAGS = r' --buildroot %(rpm_build_root)s')
+
+prog = env.Install( '/bin/' , Program( 'src/main.c') )
+
+env.Package( NAME = 'foo',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ X_RPM_GROUP = 'Application/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog ],
+ SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
+ )
+
+env.Package( NAME = 'foo2',
+ VERSION = '1.2.3',
+ PACKAGEVERSION = 0,
+ PACKAGETYPE = 'rpm',
+ LICENSE = 'gpl',
+ SUMMARY = 'balalalalal',
+ X_RPM_GROUP = 'Application/fu',
+ X_RPM_INSTALL = r'%(_python_)s %(scons)s --debug=tree --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"',
+ DESCRIPTION = 'this should be really really long',
+ source = [ prog ],
+ )
+
+
+
+env.Alias( 'install', prog )
+""" % locals())
+
+test.run(arguments='', stderr = None)
+
+src_rpm = 'foo-1.2.3-0.src.rpm'
+machine_rpm = 'foo-1.2.3-0.%s.rpm' % machine
+src_rpm2 = 'foo2-1.2.3-0.src.rpm'
+machine_rpm2 = 'foo2-1.2.3-0.%s.rpm' % machine
+
+test.must_exist( machine_rpm )
+test.must_exist( src_rpm )
+
+test.must_exist( machine_rpm2 )
+test.must_exist( src_rpm2 )
+
+test.must_not_exist( 'bin/main' )
+test.fail_test( not os.popen('rpm -qpl %s' % machine_rpm).read()=='/bin/main\n')
+test.fail_test( not os.popen('rpm -qpl %s' % src_rpm).read()=='foo-1.2.3.spec\nfoo-1.2.3.tar.gz\n')
+
+test.pass_test()
test = TestSCons.TestSCons()
+tar = test.detect('TAR', 'tar')
+
+if not tar:
+ test.skip_test('tar not found, skipping test\n')
+
test.write( 'main.c', '' )
test.write('SConstruct', """
prog = Install( '/bin', 'main.c' )
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Verify operation of the -q (--question) option in conjunction
+with Configure tests.
+
+(This was originally mostly copied and pasted from test/option-n.py.)
+"""
+
import os.path
import re
-import string
-import sys
import TestCmd
import TestSCons
-test = TestSCons.TestSCons()
-
-_python_ = TestSCons._python_
-
-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('SConstruct', """
-B = Builder(action=r'%(_python_)s build.py $TARGET $SOURCES')
-env = Environment(BUILDERS = { 'B' : B })
-env.B(target = 'aaa.out', source = 'aaa.in')
-env.B(target = 'bbb.out', source = 'bbb.in')
-""" % locals())
-
-test.write('aaa.in', "aaa.in\n")
-test.write('bbb.in', "bbb.in\n")
-
-test.run(arguments = '-q aaa.out', status = 1)
-
-test.fail_test(os.path.exists(test.workpath('aaa.out')))
-
-test.run(arguments = 'aaa.out')
-
-test.fail_test(test.read('aaa.out') != "aaa.in\n")
-
-test.run(arguments = '-q aaa.out', status = 0)
+test = TestSCons.TestSCons(match = TestCmd.match_re_dotall)
-test.run(arguments = '--question bbb.out', status = 1)
+test.write('aaa.in', 'Hello world\n')
-test.fail_test(os.path.exists(test.workpath('bbb.out')))
-
-test.run(arguments = 'bbb.out')
-
-test.fail_test(test.read('bbb.out') != "bbb.in\n")
-
-test.run(arguments = '--question bbb.out', status = 0)
-
-
-# test -q in conjunction with Configure Tests
-# mostly copy&paste from test/option-n.py
-test.subdir('configure')
-test.match_func = TestCmd.match_re_dotall
-test.write('configure/aaa.in', 'Hello world')
-test.write('configure/SConstruct',
-"""def userAction(target,source,env):
+test.write('SConstruct', """\
+def userAction(target,source,env):
import shutil
shutil.copyfile( str(source[0]), str(target[0]))
env.B(target='aaa.out', source='aaa.in')
""")
+
# test that conf_dir isn't created and an error is raised
stderr=r"""
scons: \*\*\* Cannot create configure directory "config\.test" within a dry-run\.
File \S+, line \S+, in \S+
"""
-test.run(arguments="-q aaa.out",stderr=stderr,status=2,
- chdir=test.workpath("configure"))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+test.run(arguments="-q aaa.out",stderr=stderr,status=2)
+
+test.must_not_exist(test.workpath("config.test"))
+test.must_not_exist(test.workpath("config.log"))
# test that targets are not built, if conf_dir exists.
# verify that .cache and config.log are not created.
scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
File \S+, line \S+, in \S+
""" % re.escape(os.path.join("config.test", "conftest_0.in"))
-test.subdir(['configure','config.test'])
-test.run(arguments="-q aaa.out",stderr=stderr,status=2,
- chdir=test.workpath("configure"))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
- ".cache")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
- "conftest_0")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.test",
- "conftest_0.in")))
-test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+
+test.subdir('config.test')
+
+test.run(arguments="-q aaa.out",stderr=stderr,status=2)
+
+test.must_not_exist(test.workpath("config.test", ".cache"))
+test.must_not_exist(test.workpath("config.test", "conftest_0"))
+test.must_not_exist(test.workpath("config.test", "conftest_0.in"))
+test.must_not_exist(test.workpath("config.log"))
# test that no error is raised, if all targets are up-to-date. In this
# case .cache and config.log shouldn't be created
stdout=test.wrap_stdout(build_str='cp aaa.in aaa.out\n',
read_str="""Executing Custom Test ... yes
""")
-test.run(stdout=stdout,arguments="aaa.out",status=0,chdir=test.workpath("configure"))
-log1_mtime = os.path.getmtime(test.workpath("configure","config.log"))
-test.run(arguments="-q aaa.out",status=0,
- chdir=test.workpath("configure"))
-log2_mtime = os.path.getmtime(test.workpath("configure","config.log"))
+
+test.run(stdout=stdout,arguments="aaa.out",status=0)
+
+log1_mtime = os.path.getmtime(test.workpath("config.log"))
+
+test.run(arguments="-q aaa.out",status=0)
+log2_mtime = os.path.getmtime(test.workpath("config.log"))
test.fail_test( log1_mtime != log2_mtime )
test.pass_test()
--- /dev/null
+#!/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 -q (--question) option.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_python_ = TestSCons._python_
+
+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('SConstruct', """
+B = Builder(action=r'%(_python_)s build.py $TARGET $SOURCES')
+env = Environment(BUILDERS = { 'B' : B })
+env.B(target = 'aaa.out', source = 'aaa.in')
+env.B(target = 'bbb.out', source = 'bbb.in')
+""" % locals())
+
+test.write('aaa.in', "aaa.in\n")
+test.write('bbb.in', "bbb.in\n")
+
+test.run(arguments = '-q aaa.out', status = 1)
+
+test.must_not_exist(test.workpath('aaa.out'))
+
+test.run(arguments = 'aaa.out')
+
+test.must_match('aaa.out', "aaa.in\n")
+
+test.run(arguments = '-q aaa.out', status = 0)
+
+test.run(arguments = '--question bbb.out', status = 1)
+
+test.must_not_exist(test.workpath('bbb.out'))
+
+test.run(arguments = 'bbb.out')
+
+test.must_match('bbb.out', "bbb.in\n")
+
+test.run(arguments = '--question bbb.out', status = 0)
+
+test.pass_test()
--- /dev/null
+#!/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 operation of the -q (--question) option when a specified
+target has no builder.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+_python_ = TestSCons._python_
+
+test.write('SConstruct', """
+""")
+
+test.run(arguments = '-q no_such_target', status = 1)
+
+test.run(arguments = '--question no_such_target', status = 1)
+
+test.pass_test()
else:
expect_python = python
+def escape(s):
+ return string.replace(s, '\\', '\\\\')
+
+expect_python = escape(expect_python)
+expect_workpath_pass_py = escape(workpath_pass_py)
+expect_workpath_fail_py = escape(workpath_fail_py)
+expect_workpath_no_result_py = escape(workpath_no_result_py)
+
expect_stdout = """\
-%(expect_python)s -tt %(workpath_fail_py)s
+%(expect_python)s -tt %(expect_workpath_fail_py)s
FAILING TEST STDOUT
-%(expect_python)s -tt %(workpath_no_result_py)s
+%(expect_python)s -tt %(expect_workpath_no_result_py)s
NO RESULT TEST STDOUT
-%(expect_python)s -tt %(workpath_pass_py)s
+%(expect_python)s -tt %(expect_workpath_pass_py)s
PASSING TEST STDOUT
Failed the following test:
else:
expect_python = python
+def escape(s):
+ return string.replace(s, '\\', '\\\\')
+
+expect_python = escape(expect_python)
+expect_workpath_pass_py = escape(workpath_pass_py)
+expect_workpath_fail_py = escape(workpath_fail_py)
+expect_workpath_no_result_py = escape(workpath_no_result_py)
+
expect_stdout = """\
-%(expect_python)s -tt %(workpath_fail_py)s
+%(expect_python)s -tt %(expect_workpath_fail_py)s
FAILING TEST STDOUT
-%(expect_python)s -tt %(workpath_no_result_py)s
+%(expect_python)s -tt %(expect_workpath_no_result_py)s
NO RESULT TEST STDOUT
-%(expect_python)s -tt %(workpath_pass_py)s
+%(expect_python)s -tt %(expect_workpath_pass_py)s
PASSING TEST STDOUT
Failed the following test:
mypython = os.path.join(head, dir, os.path.pardir, dir, python)
-if re.search('\s', mypython):
- _mypython_ = '"' + mypython + '"'
-else:
- _mypython_ = mypython
-
test.subdir('test')
test.write_passing_test(['test', 'pass.py'])
-# NOTE: The "test/fail.py : FAIL" and "test/pass.py : PASS" lines both
-# have spaces at the end.
+# NOTE: The "test/pass.py : PASS" line has spaces at the end.
expect = r"""qmtest.py run --output results.qmr --format none --result-stream="scons_tdb.AegisChangeStream" --context python="%(mypython)s" test
--- TEST RESULTS -------------------------------------------------------------
1 (100%%) tests PASS
""" % locals()
-test.run(arguments = '-P %s test' % _mypython_, stdout = expect)
+test.run(arguments = ['-P', mypython, 'test'], stdout = expect)
test.pass_test()
--- /dev/null
+#!/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 print .sconsign files with Configure context
+info in them (which have different BuildInfo entries).
+"""
+
+import os.path
+
+import TestSCons
+import TestSConsign
+
+_obj = TestSCons._obj
+
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+
+CC = test.detect('CC', norm=1)
+CC_dir, CC_file = os.path.split(CC)
+
+# Note: We don't use os.path.join() representations of the file names
+# in the expected output because paths in the .sconsign files are
+# canonicalized to use / as the separator.
+
+_sconf_temp_conftest_0_c = '.sconf_temp/conftest_0.c'
+
+test.write('SConstruct', """
+env = Environment()
+import os
+env.AppendENVPath('PATH', os.environ['PATH'])
+conf = Configure(env)
+r1 = conf.CheckCHeader( 'math.h' )
+env = conf.Finish()
+""")
+
+test.run(arguments = '.')
+
+sig_re = r'[0-9a-fA-F]{32}'
+date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
+
+# Note: There's a space at the end of the '.*': line, because the
+# Value node being printed actually begins with a newline. It would
+# probably be good to change that to a repr() of the contents.
+expect = r"""=== .:
+SConstruct: None \d+ \d+
+=== .sconf_temp:
+conftest_0.c:
+ '.*':
+#include "math.h"
+
+
+ %(sig_re)s \[.*\]
+conftest_0%(_obj)s:
+ %(_sconf_temp_conftest_0_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+=== %(CC_dir)s:
+%(CC_file)s: %(sig_re)s \d+ \d+
+""" % locals()
+
+test.run_sconsign(arguments = ".sconsign",
+ stdout = expect)
+
+test.pass_test()
using the signatures in an SConsignFile().
"""
+import os.path
+
import TestSConsign
test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+CC = test.detect('CC', norm=1)
+CC_dir, CC_file = os.path.split(CC)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
+
test.subdir('sub1', 'sub2')
+# Note: We don't use os.path.join() representations of the file names
+# in the expected output because paths in the .sconsign files are
+# canonicalized to use / as the separator.
+
+sub1_hello_c = 'sub1/hello.c'
+sub1_hello_obj = 'sub1/hello.obj'
+sub2_hello_c = 'sub2/hello.c'
+sub2_hello_obj = 'sub2/hello.obj'
+sub2_inc1_h = 'sub2/inc1.h'
+sub2_inc2_h = 'sub2/inc2.h'
+
test.write(['SConstruct'], """\
SConsignFile()
env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
test.run(arguments = '--implicit-cache .')
+sig_re = r'[0-9a-fA-F]{32}'
+
test.run_sconsign(arguments = ".sconsign",
- stdout = """\
+ stdout = r"""=== .:
+SConstruct: None \d+ \d+
+=== %(CC_dir)s:
+%(CC_file)s: %(sig_re)s \d+ \d+
=== sub1:
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
+hello.c: %(sig_re)s \d+ \d+
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
=== sub2:
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+hello.c: %(sig_re)s \d+ \d+
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+inc1.h: %(sig_re)s \d+ \d+
+inc2.h: %(sig_re)s \d+ \d+
+""" % locals())
test.run_sconsign(arguments = "--raw .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+SConstruct: {'csig': None, 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+=== %(CC_dir)s:
+%(CC_file)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
=== sub1:
-hello.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.c: \S+
+hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
+hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
=== sub2:
-hello.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub2_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
+hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub2_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub2_inc1_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub2_inc2_h)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
+inc1.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+inc2.h: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+""" % locals())
-test.run_sconsign(arguments = "-v .sconsign",
- stdout = """\
+expect = r"""=== .:
+SConstruct:
+ csig: None
+ timestamp: \d+
+ size: \d+
+=== %(CC_dir)s:
+%(CC_file)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
=== sub1:
+hello.c:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
hello.exe:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.obj: \S+
+ %(sub1_hello_obj)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(LINK)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
hello.obj:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.c: \S+
+ %(sub1_hello_c)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(CC)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
=== sub2:
+hello.c:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
hello.exe:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.obj: \S+
+ %(sub2_hello_obj)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(LINK)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
hello.obj:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+ %(sub2_hello_c)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(sub2_inc1_h)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(sub2_inc2_h)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(CC)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
+inc1.h:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+inc2.h:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+""" % locals()
-test.run_sconsign(arguments = "-b -v .sconsign",
- stdout = """\
-=== sub1:
-hello.exe:
- bsig: \S+
-hello.obj:
- bsig: \S+
-=== sub2:
-hello.exe:
- bsig: \S+
-hello.obj:
- bsig: \S+
-""")
+test.run_sconsign(arguments = "-v .sconsign", stdout=expect)
test.run_sconsign(arguments = "-c -v .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+SConstruct:
+ csig: None
+=== %(CC_dir)s:
+%(CC_file)s:
+ csig: %(sig_re)s
=== sub1:
+hello.c:
+ csig: %(sig_re)s
hello.exe:
- csig: None
+ csig: %(sig_re)s
hello.obj:
- csig: None
+ csig: %(sig_re)s
=== sub2:
+hello.c:
+ csig: %(sig_re)s
hello.exe:
- csig: None
+ csig: %(sig_re)s
hello.obj:
- csig: None
-""")
+ csig: %(sig_re)s
+inc1.h:
+ csig: %(sig_re)s
+inc2.h:
+ csig: %(sig_re)s
+""" % locals())
test.run_sconsign(arguments = "-s -v .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+SConstruct:
+ size: \d+
+=== %(CC_dir)s:
+%(CC_file)s:
+ size: \d+
=== sub1:
+hello.c:
+ size: \d+
hello.exe:
size: \d+
hello.obj:
size: \d+
=== sub2:
+hello.c:
+ size: \d+
hello.exe:
size: \d+
hello.obj:
size: \d+
-""")
+inc1.h:
+ size: \d+
+inc2.h:
+ size: \d+
+""" % locals())
test.run_sconsign(arguments = "-t -v .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+SConstruct:
+ timestamp: \d+
+=== %(CC_dir)s:
+%(CC_file)s:
+ timestamp: \d+
=== sub1:
+hello.c:
+ timestamp: \d+
hello.exe:
timestamp: \d+
hello.obj:
timestamp: \d+
=== sub2:
+hello.c:
+ timestamp: \d+
hello.exe:
timestamp: \d+
hello.obj:
timestamp: \d+
-""")
+inc1.h:
+ timestamp: \d+
+inc2.h:
+ timestamp: \d+
+""" % locals())
test.run_sconsign(arguments = "-e hello.obj .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+=== %(CC_dir)s:
=== sub1:
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
=== sub2:
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals(),
+ stderr = r"""sconsign: no entry `hello.obj' in `\.'
+sconsign: no entry `hello.obj' in `%(CC_dir)s'
+""" % locals())
test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign",
- stdout = """\
+ stdout = r"""=== .:
+=== %(CC_dir)s:
=== sub1:
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
=== sub2:
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals(),
+ stderr = r"""sconsign: no entry `hello.obj' in `\.'
+sconsign: no entry `hello.exe' in `\.'
+sconsign: no entry `hello.obj' in `\.'
+sconsign: no entry `hello.obj' in `%(CC_dir)s'
+sconsign: no entry `hello.exe' in `%(CC_dir)s'
+sconsign: no entry `hello.obj' in `%(CC_dir)s'
+""" % locals())
#test.run_sconsign(arguments = "-i -v .sconsign",
-# stdout = """\
-#=== sub1:
+# stdout = r"""=== sub1:
#hello.exe:
# implicit:
-# hello.obj: \S+
+# hello.obj: %(sig_re)s
#hello.obj:
# implicit:
-# hello.c: \S+
+# hello.c: %(sig_re)s
#=== sub2:
#hello.exe:
# implicit:
-# hello.obj: \S+
+# hello.obj: %(sig_re)s
#hello.obj:
# implicit:
-# hello.c: \S+
-# inc1.h: \S+
-# inc2.h: \S+
-#""")
+# hello.c: %(sig_re)s
+# inc1.h: %(sig_re)s
+# inc2.h: %(sig_re)s
+#inc1.h: %(sig_re)s
+#inc2.h: %(sig_re)s
+#""" % locals())
test.pass_test()
test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
+
+# Note: We don't use os.path.join() representations of the file names
+# in the expected output because paths in the .sconsign files are
+# canonicalized to use / as the separator.
+
+sub1_hello_c = 'sub1/hello.c'
+sub1_hello_obj = 'sub1/hello.obj'
+
def re_sep(*args):
import os.path
import re
test.run(arguments = '. --max-drift=1')
+sig_re = r'[0-9a-fA-F]{32}'
+date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
+
test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/.sconsign",
- stdout = """\
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-""")
+ stdout = r"""hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: None \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: None \d+ \d+
+ %(CC)s: None \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals())
test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r sub1/.sconsign",
- stdout = """\
-hello.exe: \S+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
- hello.obj: \S+
-hello.obj: \S+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
- hello.c: \S+
-""")
+ stdout = r"""hello.exe: %(sig_re)s '%(date_re)s' \d+
+ %(sub1_hello_obj)s: %(sig_re)s '%(date_re)s' \d+
+ %(LINK)s: None '%(date_re)s' \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s '%(date_re)s' \d+
+ %(sub1_hello_c)s: None '%(date_re)s' \d+
+ %(CC)s: None '%(date_re)s' \d+
+ %(sig_re)s \[.*\]
+""" % locals())
test.pass_test()
test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
+
test.subdir('sub1', 'sub2')
+# Note: We don't use os.path.join() representations of the file names
+# in the expected output because paths in the .sconsign files are
+# canonicalized to use / as the separator.
+
+sub1_hello_c = 'sub1/hello.c'
+sub1_hello_obj = 'sub1/hello.obj'
+
test.write('SConstruct', """
SConsignFile('my_sconsign')
SourceSignatures('timestamp')
test.run(arguments = '. --max-drift=1')
-expect = """\
-=== sub1:
-hello.exe: \d+ None \d+ \d+
- hello.obj: \d+
-hello.obj: \d+ None \d+ \d+
- hello.c: \d+
-"""
-
-expect_r = """\
-=== sub1:
-hello.exe: \d+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
- hello.obj: \d+
-hello.obj: \d+ None '\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d' \d+
- hello.c: \d+
-"""
+sig_re = r'[0-9a-fA-F]{32}'
+date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
+
+expect = r"""=== sub1:
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: None \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: None \d+ \d+
+ %(CC)s: None \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals()
+
+expect_r = """=== sub1:
+hello.exe: %(sig_re)s '%(date_re)s' \d+
+ %(sub1_hello_obj)s: %(sig_re)s '%(date_re)s' \d+
+ %(LINK)s: None '%(date_re)s' \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s '%(date_re)s' \d+
+ %(sub1_hello_c)s: None '%(date_re)s' \d+
+ %(CC)s: None '%(date_re)s' \d+
+ %(sig_re)s \[.*\]
+""" % locals()
common_flags = '-e hello.exe -e hello.obj -d sub1'
test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
-def re_sep(*args):
- import os.path
- import re
- return re.escape(apply(os.path.join, args))
+CC = test.detect('CC', norm=1)
+LINK = test.detect('LINK', norm=1)
+if LINK is None: LINK = CC
test.subdir('sub1', 'sub2')
+# Note: We don't use os.path.join() representations of the file names
+# in the expected output because paths in the .sconsign files are
+# canonicalized to use / as the separator.
+
+sub1_hello_c = 'sub1/hello.c'
+sub1_hello_obj = 'sub1/hello.obj'
+sub2_hello_c = 'sub2/hello.c'
+sub2_hello_obj = 'sub2/hello.obj'
+sub2_inc1_h = 'sub2/inc1.h'
+sub2_inc2_h = 'sub2/inc2.h'
+
test.write(['SConstruct'], """
SConsignFile(None)
env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
#define STRING2 "inc2.h"
""")
-test.run(arguments = '--implicit-cache .')
+test.run(arguments = '--implicit-cache --tree=prune .')
-test.run_sconsign(arguments = "sub1/.sconsign",
- stdout = """\
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-""")
+sig_re = r'[0-9a-fA-F]{32}'
+
+expect = r"""hello.c: %(sig_re)s \d+ \d+
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals()
+
+test.run_sconsign(arguments = "sub1/.sconsign", stdout=expect)
+#test.run_sconsign(arguments = "sub1/.sconsign")
+#print test.stdout()
test.run_sconsign(arguments = "--raw sub1/.sconsign",
- stdout = """\
-hello.exe: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.obj: \S+
-hello.obj: {'bsig': '\S+', 'size': \d+L?, 'timestamp': \d+}
- hello.c: \S+
-""")
+ stdout = r"""hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(LINK)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
+hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sub1_hello_c)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(CC)s: {'csig': '%(sig_re)s', 'timestamp': \d+, 'size': \d+L?, '_version_id': 1}
+ %(sig_re)s \[.*\]
+""" % locals())
test.run_sconsign(arguments = "-v sub1/.sconsign",
- stdout = """\
+ stdout = r"""hello.c:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
hello.exe:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.obj: \S+
+ %(sub1_hello_obj)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(LINK)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
hello.obj:
- bsig: \S+
- csig: None
+ csig: %(sig_re)s
timestamp: \d+
size: \d+
implicit:
- hello.c: \S+
-""")
-
-test.run_sconsign(arguments = "-b -v sub1/.sconsign",
- stdout = """\
-hello.exe:
- bsig: \S+
-hello.obj:
- bsig: \S+
-""")
+ %(sub1_hello_c)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ %(CC)s:
+ csig: %(sig_re)s
+ timestamp: \d+
+ size: \d+
+ action: %(sig_re)s \[.*\]
+""" % locals())
test.run_sconsign(arguments = "-c -v sub1/.sconsign",
- stdout = """\
+ stdout = r"""hello.c:
+ csig: %(sig_re)s
hello.exe:
- csig: None
+ csig: %(sig_re)s
hello.obj:
- csig: None
-""")
+ csig: %(sig_re)s
+""" % locals())
test.run_sconsign(arguments = "-s -v sub1/.sconsign",
- stdout = """\
+ stdout = r"""hello.c:
+ size: \d+
hello.exe:
size: \d+
hello.obj:
size: \d+
-""")
+""" % locals())
test.run_sconsign(arguments = "-t -v sub1/.sconsign",
- stdout = """\
+ stdout = r"""hello.c:
+ timestamp: \d+
hello.exe:
timestamp: \d+
hello.obj:
timestamp: \d+
-""")
+""" % locals())
test.run_sconsign(arguments = "-e hello.obj sub1/.sconsign",
- stdout = """\
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-""")
+ stdout = r"""hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals())
test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj sub1/.sconsign",
- stdout = """\
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-""")
-
-# XXX NOT SURE IF THIS IS RIGHT!
-sub2_inc1_h = re_sep('sub2', 'inc1.h')
-sub2_inc2_h = re_sep('sub2', 'inc2.h')
+ stdout = r"""hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub1_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals())
test.run_sconsign(arguments = "sub2/.sconsign",
- stdout = """\
-hello.exe: \S+ None \d+ \d+
- hello.obj: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-""")
+ stdout = r"""hello.c: %(sig_re)s \d+ \d+
+hello.exe: %(sig_re)s \d+ \d+
+ %(sub2_hello_obj)s: %(sig_re)s \d+ \d+
+ %(LINK)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+inc1.h: %(sig_re)s \d+ \d+
+inc2.h: %(sig_re)s \d+ \d+
+""" % locals())
#test.run_sconsign(arguments = "-i -v sub2/.sconsign",
-# stdout = """\
-#hello.exe:
+# stdout = r"""hello.c: %(sig_re)s \d+ \d+
+#hello.exe: %(sig_re)s \d+ \d+
# implicit:
-# hello.obj: \S+ None \d+ \d+
-#hello.obj:
+# hello.obj: %(sig_re)s \d+ \d+
+#hello.obj: %(sig_re)s \d+ \d+
# implicit:
-# hello.c: None \S+ \d+ \d+
-# inc1.h: None \S+ \d+ \d+
-# inc2.h: None \S+ \d+ \d+
-#""")
+# hello.c: %(sig_re)s \d+ \d+
+# inc1.h: %(sig_re)s \d+ \d+
+# inc2.h: %(sig_re)s \d+ \d+
+#""" % locals())
test.run_sconsign(arguments = "-e hello.obj sub2/.sconsign sub1/.sconsign",
- stdout = """\
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
- inc1.h: \S+
- inc2.h: \S+
-hello.obj: \S+ None \d+ \d+
- hello.c: \S+
-""")
+ stdout = r"""hello.obj: %(sig_re)s \d+ \d+
+ %(sub2_hello_c)s: %(sig_re)s \d+ \d+
+ %(sub2_inc1_h)s: %(sig_re)s \d+ \d+
+ %(sub2_inc2_h)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+hello.obj: %(sig_re)s \d+ \d+
+ %(sub1_hello_c)s: %(sig_re)s \d+ \d+
+ %(CC)s: %(sig_re)s \d+ \d+
+ %(sig_re)s \[.*\]
+""" % locals())
test.pass_test()
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Verify falling back to 'timestamp' behavior if there is no native
+hashlib and no underlying md5 module available.
+"""
+
import imp
import os
import os.path
test = TestSCons.TestSCons()
+try:
+ file, name, desc = imp.find_module('hashlib')
+except ImportError:
+ pass
+else:
+ msg = "This version of Python has a 'hashlib' module.\n" + \
+ "Skipping test of falling back to timestamps.\n"
+ test.skip_test(msg)
+
try:
file, name, desc = imp.find_module('md5')
except ImportError:
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
-import os.path
+"""
+Verify appropriate printing of "is up to date" messages.
+"""
+
import string
-import sys
+
import TestSCons
_python_ = TestSCons._python_
test.run(arguments = 'f1.out f2.out f3.out f4.out', stdout = expect)
-test.pass_test()
+# Make sure all of the "up to date" messages get printed even when -j
+# is used. This broke during signature refactoring development.
+expected_lines = [
+ "scons: `f1.out' is up to date.",
+ "scons: `f2.out' is up to date.",
+ "scons: `f3.out' is up to date.",
+ "scons: `f4.out' is up to date.",
+]
+test.run(options = '-j4 f1.out f2.out f3.out f4.out')
+stdout = test.stdout()
+
+missing = []
+for line in expected_lines:
+ if string.find(stdout, line) == -1:
+ missing.append(line)
+
+if missing:
+ print "Missing the following expected lines:"
+ for line in missing:
+ print line
+ print "STDOUT =========="
+ print stdout
+ test.fail_test()
+
+test.pass_test()