test.subdir('subdir', ...)
+ test.rmdir('subdir', ...)
+
test.write('file', "contents\n")
test.write(['subdir', 'file'], "contents\n")
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.26.D001 2007/08/20 21:58:58 knight"
-__version__ = "0.26"
+__revision__ = "TestCmd.py 0.30.D001 2007/10/01 16:53:55 knight"
+__version__ = "0.30"
import os
import os.path
raise ValueError, "mode must begin with 'r'"
return open(file, mode).read()
+ def rmdir(self, dir):
+ """Removes the specified dir name.
+ The dir name may be a list, in which case the elements are
+ concatenated with the os.path.join() method. The dir is
+ assumed to be under the temporary working directory unless it
+ is an absolute path name.
+ The dir must be empty.
+ """
+ dir = self.canonicalize(dir)
+ os.rmdir(dir)
+
def run(self, program = None,
interpreter = None,
arguments = None,
test.must_contain('file', 'required text\n')
+ test.must_contain_lines(lines, output)
+
test.must_exist('file1', ['file2', ...])
test.must_match('file', "expected contents\n")
test.must_not_be_writable('file1', ['file2', ...])
+ test.must_not_contain_lines(lines, output)
+
test.must_not_exist('file1', ['file2', ...])
test.run(options = "options to be prepended to arguments",
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight"
-__version__ = "0.26"
+__revision__ = "TestCommon.py 0.30.D001 2007/10/01 16:53:55 knight"
+__version__ = "0.30"
import os
import os.path
from TestCmd import __all__
__all__.extend([ 'TestCommon',
- 'TestFailed',
- 'TestNoResult',
'exe_suffix',
'obj_suffix',
'shobj_suffix',
missing.append(f)
return existing, missing
-class TestFailed(Exception):
- def __init__(self, args=None):
- self.args = args
-
-class TestNoResult(Exception):
- def __init__(self, args=None):
- self.args = args
-
if os.name == 'posix':
def _failed(self, status = 0):
if self.status is None or status is None:
return None
- if os.WIFSIGNALED(self.status):
- return None
return _status(self) != status
def _status(self):
if os.WIFEXITED(self.status):
return os.WEXITSTATUS(self.status)
+ elif os.WIFSIGNALED(self.status):
+ return os.WTERMSIG(self.status)
else:
return None
elif os.name == 'nt':
print file_contents
self.fail_test(not contains)
+ def must_contain_lines(self, lines, output, title=None):
+ if title is None:
+ title = 'output'
+
+ missing = filter(lambda l, o=output: string.find(o, l) == -1, lines)
+
+ if missing:
+ print "Missing lines from %s:" % title
+ print string.join(missing, '\n')
+ print "%s ============================================================" % title
+ print output
+ self.fail_test()
+
def must_exist(self, *files):
"""Ensures that the specified file(s) must exist. An individual
file be specified as a list of directory names, in which case the
self.diff(expect, file_contents, 'contents ')
raise
+ def must_not_contain_lines(self, lines, output=None, title=None):
+ if title is None:
+ title = 'output'
+
+ unexpected = filter(lambda l, o=output: string.find(o, l) != -1, lines)
+
+ if unexpected:
+ print "Unexpected lines in %s:" % title
+ print string.join(unexpected, '\n')
+ print "%s ============================================================" % title
+ print output
+ self.fail_test()
+
def must_not_exist(self, *files):
"""Ensures that the specified file(s) must not exist.
An individual file be specified as a list of directory names, in
apply(TestCmd.run, [self], kw)
except KeyboardInterrupt:
raise
- except:
+ except Exception, e:
print self.banner('STDOUT ')
- print self.stdout()
+ try:
+ print self.stdout()
+ except IndexError:
+ pass
print self.banner('STDERR ')
- print self.stderr()
- raise
+ try:
+ print self.stderr()
+ except IndexError:
+ pass
+ raise e
if _failed(self, status):
expect = ''
if status != 0:
print self.stdout()
print self.banner('STDERR ')
print self.stderr()
- raise TestFailed
+ self.fail_test()
if not stdout is None and not match(self.stdout(), stdout):
self.diff(stdout, self.stdout(), 'STDOUT ')
stderr = self.stderr()
if stderr:
print self.banner('STDERR ')
print stderr
- raise TestFailed
+ self.fail_test()
if not stderr is None and not match(self.stderr(), stderr):
print self.banner('STDOUT ')
print self.stdout()
self.diff(stderr, self.stderr(), 'STDERR ')
- raise TestFailed
+ self.fail_test()
def skip_test(self, message="Skipping test.\n"):
"""Skips a test.
import stat
import string
import sys
+import tempfile
project = 'scons'
default_version = '0.97.0'
platform = distutils.util.get_platform()
+# Re-exporting LD_LIBRARY_PATH is necessary if the Python version was
+# built with the --enable-shared option.
+
ENV = { 'PATH' : os.environ['PATH'] }
-for key in ['LOGNAME', 'PYTHONPATH']:
+for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']:
if os.environ.has_key(key):
ENV[key] = os.environ[key]
},
}
-# The RPM spec file we generate will just execute "python", not
-# necessarily the one in sys.executable. If that version of python has
-# a distutils that knows about Python eggs, then setup.py will generate
-# a .egg-info file. Check for whether or not to add it to the expected
-# RPM files by executing "python" in a subshell.
+# Figure out the name of a .egg-info file that might be generated
+# as part of the RPM package. There are two complicating factors.
+#
+# First, the RPM spec file we generate will just execute "python", not
+# necessarily the one in sys.executable. If *that* version of python has
+# a distutils that knows about Python eggs, then setup.py will generate a
+# .egg-info file, so we have to execute any distutils logic in a subshell.
+#
+# Second, we can't just have the subshell check for the existence of the
+# distutils.command.install_egg_info module and generate the expected
+# file name by hand, the way we used to, because different systems can
+# have slightly different .egg-info naming conventions. (Specifically,
+# Ubuntu overrides the default behavior to remove the Python version
+# string from the .egg-info file name.) The right way to do this is to
+# actually call into the install_egg_info() class to have it generate
+# the expected name for us.
+#
+# This is all complicated enough that we do it by writing an in-line
+# script to a temporary file and then feeding it to a separate invocation
+# of "python" to tell us the actual name of the generated .egg-info file.
-cmd = "python -c 'import distutils.command.install_egg_info' > /dev/null 2>&1"
-import_egg_error = os.system(cmd)
+print_egg_info_name = """
+try:
+ from distutils.dist import Distribution
+ from distutils.command.install_egg_info import install_egg_info
+except ImportError:
+ pass
+else:
+ dist = Distribution({'name' : "scons", 'version' : '%s'})
+ i = install_egg_info(dist)
+ i.finalize_options()
+ import os.path
+ print os.path.split(i.outputs[0])[1]
+""" % version
-if not import_egg_error:
- egg_info_file = 'scons-' + version + '.egg-info'
- python_scons['extra_rpm_files'].append(egg_info_file)
+try:
+ fd, tfname = tempfile.mkstemp()
+ tfp = os.fdopen(fd, "w")
+ tfp.write(print_egg_info_name)
+ tfp.close()
+ egg_info_file = os.popen("python %s" % tfname).read()[:-1]
+ if egg_info_file:
+ python_scons['extra_rpm_files'].append(egg_info_file)
+finally:
+ try:
+ os.unlink(tfname)
+ except EnvironmentError:
+ pass
#
# The original packaging scheme would have have required us to push
def CCCom(target, source, env):
target = str(target[0])
- f = open(target, "wb")
+ fp = open(target, "wb")
+ def process(source_file, fp=fp):
+ for line in open(source_file, "rb").readlines():
+ m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
+ if m:
+ include = m.group(1)
+ for d in [str(env.Dir('$CPPPATH')), '.']:
+ f = os.path.join(d, include)
+ if os.path.exists(f):
+ process(f)
+ break
+ elif line[:11] != "STRIP CCCOM":
+ fp.write(line)
for src in map(str, source):
- lines = open(src, "rb").readlines()
- lines = filter(lambda l: l[:11] != "STRIP CCCOM", lines)
- f.write(string.join(lines))
- f.close()
+ process(src)
+ fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
+ fp.close()
public_class_re = re.compile('^public class (\S+)', re.MULTILINE)
return lines
def command_touch(args, c, test, dict):
- time.sleep(1)
+ if args[0] == '-t':
+ t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
+ times = (t, t)
+ args = args[2:]
+ else:
+ time.sleep(1)
+ times = None
for file in args:
if not os.path.isabs(file):
file = os.path.join(test.workpath('WORK'), file)
if not os.path.exists(file):
open(file, 'wb')
- os.utime(file, None)
+ os.utime(file, times)
return []
def command_edit(args, c, test, dict):
elif lines:
content = string.join(lines, '\n' + p)
if content:
+ content = re.sub(' at 0x[0-9a-fA-F]*\>', ' at 0x700000>', content)
content = string.replace(content, '<', '<')
content = string.replace(content, '>', '>')
sys.stdout.write(p + content + '\n')
in which case only the specified targets will be built
(along with any derived files on which they depend).
-Specifying "cleanup" targets in SConscript files is not
-necessary. The
+Specifying "cleanup" targets in SConscript files is not usually necessary.
+The
.B -c
flag removes all files
necessary to build the specified target:
Search the specified repository for any input and target
files not found in the local directory hierarchy. Multiple
.B -Y
-options may specified, in which case the
+options may be specified, in which case the
repositories are searched in the order specified.
.SH CONFIGURATION FILE REFERENCE
Additionally, a specific set of tools
with which to initialize the environment
-may specified as an optional keyword argument:
+may be specified as an optional keyword argument:
.ES
env = Environment(tools = ['msvc', 'lex'])
environment's builder methods.
The arguments to the builder methods are
.B target
-(a list of target files)
+(a list of targets to be built,
+usually file names)
and
.B source
-(a list of source files).
+(a list of sources to be built,
+usually file names).
Because long lists of file names
can lead to a lot of quoting,
env.Program('bar', source = string.split('bar.c foo.c'))
.EE
+Target and source file names
+that are not absolute path names
+(that is, do not begin with
+.B /
+on POSIX systems
+or
+.B \\
+on Windows systems,
+with or without
+an optional drive letter)
+are interpreted relative to the directory containing the
+.B SConscript
+file being read.
+An initial
+.B #
+(hash mark)
+on a path name means that the rest of the file name
+is interpreted relative to
+the directory containing
+the top-level
+.B SConstruct
+file,
+even if the
+.B #
+is followed by a directory separator character
+(slash or backslash).
+
+Examples:
+
+.ES
+# The comments describing the targets that will be built
+# assume these calls are in a SConscript file in the
+# a subdirectory named "subdir".
+
+# Builds the program "subdir/foo" from "subdir/foo.c":
+env.Program('foo', 'foo.c')
+
+# Builds the program "/tmp/bar" from "subdir/bar.c":
+env.Program('/tmp/bar', 'bar.c')
+
+# An initial '#' or '#/' are equivalent; the following
+# calls build the programs "foo" and "bar" (in the
+# top-level SConstruct directory) from "subdir/foo.c" and
+# "subdir/bar.c", respectively:
+env.Program('#foo', 'foo.c')
+env.Program('#/bar', 'bar.c')
+
+# Builds the program "other/foo" (relative to the top-level
+# SConstruct directory) from "subdir/foo.c":
+env.Program('#other/foo', 'foo.c')
+.EE
+
When the target shares the same base name
as the source and only the suffix varies,
and if the builder method has a suffix defined for the target file type,
from SCons.Script import *
.EE
-All builder methods return a list of Nodes
-that represent the target or targets that will be built.
+All builder methods return a list-like object
+containing Nodes that
+represent the target or targets that will be built.
A
.I Node
is an internal SCons object
which represents
build targets or sources.
-The returned Node(s)
+The returned Node-list object
can be passed to other builder methods as source(s)
or passed to any SCons function or method
where a filename would normally be accepted.
Or you can use the
.BR Flatten ()
-supplied by scons
+function supplied by scons
to create a list containing just the Nodes,
which may be more convenient:
print str(object)
.EE
+Note also that because Builder calls return
+a list-like object, not an actual Python list,
+you should
+.I not
+use the Python
+.B +=
+operator to append Builder results to a Python list.
+Because the list and the object are different types,
+Python will not update the original list in place,
+but will instead create a new Node-list object
+containing the concatenation of the list
+elements and the Builder results.
+This will cause problems for any other Python variables
+in your SCons configuration
+that still hold on to a reference to the original list.
+Instead, use the Python
+.B .extend()
+method to make sure the list is updated in-place.
+Example:
+
+.ES
+object_files = []
+
+# Do NOT use += as follows:
+#
+# object_files += Object('bar.c')
+#
+# It will not update the object_files list in place.
+#
+# Instead, use the .extend() method:
+object_files.extend(Object('bar.c'))
+
+.EE
+
The path name for a Node's file may be used
by passing the Node to the Python-builtin
.B str()
.RI FindInstalledFiles( )
.TP
.RI env.FindInstalledFiles( )
-Returns the list of targets setup by the
+Returns the list of targets set up by the
.B Install()
or
.B InstallAs()
builders.
This function serves as a convenient method to select the contents of
-a Binary Package.
+a binary package.
Example:
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
+which defaults to the '"."'-node. It will then return all leaves of
.B node.
These are all children which have no further children.
print str(object)
.EE
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI GetBuildFailures()
+Returns a list of exceptions for the
+actions that failed while
+attempting to build targets.
+Each element in the returned list is a
+.B BuildError
+object
+with the following attributes
+that record various aspects
+of the build failure:
+
+.B .node
+The node that was being built
+when the build failure occurred.
+
+.B .status
+The numeric exit status
+returned by the command or Python function
+that failed when trying to build the
+specified Node.
+
+.B .errstr
+The SCons error string
+describing the build failure.
+(This is often a generic
+message like "Error 2"
+to indicate that an executed
+command exited with a status of 2.)
+
+.B .filename
+The name of the file or
+directory that actually caused the failure.
+This may be different from the
+.B .node
+attribute.
+For example,
+if an attempt to build a target named
+.B sub/dir/target
+fails because the
+.B sub/dir
+directory could not be created,
+then the
+.B .node
+attribute will be
+.B sub/dir/target
+but the
+.B .filename
+attribute will be
+.BR sub/dir .
+
+.B .executor
+The SCons Executor object
+for the target Node
+being built.
+This can be used to retrieve
+the construction environment used
+for the failed action.
+
+.B .action
+The actual SCons Action object that failed.
+This will be one specific action
+out of the possible list of
+actions that would have been
+executed to build the target.
+
+.B .command
+The actual expanded command that was executed and failed,
+after expansion of
+.BR $TARGET ,
+.BR $SOURCE ,
+and other construction variables.
+
+Note that the
+.BR GetBuildFailures ()
+function
+will always return an empty list
+until any build failure has occurred,
+which means that
+.BR GetBuildFailures ()
+will always return an empty list
+while the
+.B SConscript
+files are being read.
+Its primary intended use is
+for functions that will be
+executed before SCons exits
+by passing them to the
+standard Python
+.BR atexit.register ()
+function.
+Example:
+
+.ES
+import atexit
+
+def print_build_failures():
+ from SCons.Script import GetBuildFailures
+ for bf in GetBuildFailures():
+ print "%s failed: %s" % (bf.node, bf.errstr)
+
+atexit.register(print_build_failures)
+.EE
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI GetBuildPath( file ", [" ... ])
.IR SetOption ()
for a description of the options available.
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Glob( pattern ", [" ondisk ", " source ", " strings ])
+.TP
+.RI env.Glob( pattern ", [" ondisk ", " source ", " strings ])
+Returns Nodes (or strings) that match the specified
+.IR pattern ,
+relative to the directory of the current
+.B SConscript
+file.
+The
+.BR env.Glob ()
+form performs string substition on
+.I pattern
+and returns whatever matches
+the resulting expanded pattern.
+
+The specified
+.I pattern
+uses Unix shell style metacharacters for matching:
+
+.ES
+ * matches everything
+ ? matches any single character
+ [seq] matches any character in seq
+ [!seq] matches any char not in seq
+.EE
+
+Character matches do
+.I not
+span directory separators.
+
+The
+.BR Glob ()
+knows about
+repositories
+(see the
+.BR Repository ()
+function)
+and source directories
+(see the
+.BR BuildDir ()
+function)
+and
+returns a Node (or string, if so configured)
+in the local (SConscript) directory
+if matching Node is found
+anywhere in a corresponding
+repository or source directory.
+
+The
+.B ondisk
+argument may be set to
+.B False
+(or any other non-true value)
+to disable the search for matches on disk,
+thereby only returning matches among
+already-configured File or Dir Nodes.
+The default behavior is to
+return corresponding Nodes
+for any on-disk matches found.
+
+The
+.B source
+argument may be set to
+.B True
+(or any equivalent value)
+to specify that,
+when the local directory is a
+.BR BuildDir (),
+the returned Nodes should be from the
+corresponding source directory,
+not the local directory.
+
+The
+.B strings
+argument may be set to
+.B True
+(or any equivalent value)
+to have the
+.BR Glob ()
+function return strings, not Nodes,
+that represent the matched files or directories.
+The returned strings will be relative to
+the local (SConscript) directory.
+(Note that This may make it easier to perform
+arbitrary manipulation of file names,
+but if the returned strings are
+passed to a different
+.B SConscript
+file,
+any Node translation will be relative
+to the other
+.B SConscript
+directory,
+not the original
+.B SConscript
+directory.)
+
+Example:
+
+.ES
+Program('foo', Glob('*.c'))
+.EE
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
'\".TP
'\".RI GlobalBuilders( flag )
.B Local()
method.
+'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+.TP
+.RI Requires( target ", " prerequisite )
+.TP
+.RI env.Requires( target ", " prerequisite )
+Specifies an order-only relationship
+between the specified target file(s)
+and the specified prerequisite file(s).
+The prerequisite file(s)
+will be (re)built, if necessary,
+.I before
+the target file(s),
+but the target file(s) do not actually
+depend on the prerequisites
+and will not be rebuilt simply because
+the prerequisite file(s) change.
+
+Example:
+
+.ES
+env.Requires('foo', 'file-that-must-be-built-before-foo')
+.EE
+
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
.RI Return([ vars "... , " stop= ])
.RI SourceSignatures( type )
.TP
.RI env.SourceSignatures( type )
-This function tells
+Note: Although it is not yet officially deprecated,
+use of this function is discouraged.
+See the
+.BR Decider ()
+function for a more flexible and straightforward way
+to configure SCons' decision-making.
+
+The
+.BR SourceSignatures ()
+function tells
.B scons
how to decide if a source file
(a file that is not built from any other files)
means
.B scons
decides that a source file has changed
-if its timestamp (modification time) is newer than
+if its timestamp (modification time) has changed since
the last time it was used to rebuild a particular target file.
+(Note that although this is similar to the behavior of Make,
+by default it will also rebuild if the dependency is
+.I older
+than the last time it was used to rebuild the target file.)
There is no different between the two behaviors
for Python
.RI TargetSignatures( type )
.TP
.RI env.TargetSignatures( type )
-This function tells
+Note: Although it is not yet officially deprecated,
+use of this function is discouraged.
+See the
+.BR Decider ()
+function for a more flexible and straightforward way
+to configure SCons' decision-making.
+
+The
+.BR TargetSignatures ()
+function tells
.B scons
how to decide if a target file
(a file that
means
.B scons
decides that a target file has changed
-if its timestamp (modification time) is newer than
+if its timestamp (modification time) has changed since
the last time it was used to rebuild some other target file.
+(Note that although this is similar to the behavior of Make,
+by default it will also rebuild if the dependency is
+.I older
+than the last time it was used to rebuild the target file.)
.B "source"
means
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
'\" BEGIN GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS
'\"
-'\" The descriptions below of the various SCons contruction variables
+'\" The descriptions below of the various SCons construction variables
'\" are generated from the .xml files that live next to the various
'\" Python modules in the build enginer library. If you're reading
'\" this [gnt]roff file with an eye towards patching this man page,
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
'\" END GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS
'\"
-'\" The descriptions above of the various SCons contruction variables
+'\" The descriptions above of the various SCons construction variables
'\" are generated from the .xml files that live next to the various
'\" Python modules in the build enginer library. If you're reading
'\" this [gnt]roff file with an eye towards patching this man page,
The following methods can be used to perform checks:
.TP
-.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help])
.TP
-.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ])
+.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help])
This creates a configure context, which can be used to perform checks.
.I env
specifies the environment for building the tests.
.I config_h
file is being built.
+The optional
+.B clean
+and
+.B help
+arguments can be used to suppress execution of the configuration
+tests when the
+.B -c/--clean
+or
+.B -H/-h/--help
+options are used, respectively.
+The default behavior is always to execute
+configure context tests,
+since the results of the tests may
+affect the list of targets to be cleaned
+or the help text.
+If the configure tests do not affect these,
+then you may add the
+.B clean=False
+or
+.B help=False
+arguments
+(or both)
+to avoid unnecessary test execution.
+
.EE
A created
.B Configure
env = conf.Finish()
.EE
+.TP
+.RI Configure.CheckTypeSize( self ", " type_name ", [" header ", " language ", " expect ])
+Checks for the size of a type defined by
+.BR typedef .
+.I type_name
+specifies the typedef name to check for.
+The optional
+.I header
+argument is a string
+that will be
+placed at the top
+of the test file
+that will be compiled
+to check if the function exists;
+the default is empty.
+The optional
+.I language
+argument should be
+.B C
+or
+.B C++
+and selects the compiler to be used for the check;
+the default is "C".
+The optional
+.I expect
+argument should be an integer.
+If this argument is used,
+the function will only check whether the type
+given in type_name has the expected size (in bytes).
+For example,
+.B "CheckTypeSize('short', expect = 2)"
+will return success only if short is two bytes.
+
+.ES
+.EE
+
.EE
You can define your own custom checks.
in addition to the predefined checks.
locations, or site-specific compiler options may need to be passed to the
compiler.
.B scons
-provides a mechanism for overridding construction variables from the
-command line or a text-based SConscript file through an Options
-object. To create an Options object, call the Options() function:
+provides an Options object for overridding construction variables
+on the command line:
+.ES
+$ scons VARIABLE=foo
+.EE
+The variable values can also be specified in a text-based SConscript file.
+To create an Options object, call the Options() function:
.TP
.RI Options([ files "], [" args ])
.RI Update( env ", [" args ])
This updates a construction environment
.I env
-with the customized construction variables. Normally this method is not
-called directly, but is called indirectly by passing the Options object to
+with the customized construction variables.
+Any specified variables that are
+.I not
+configured for the Options object
+will be saved and may be
+retrieved with the
+.BR UnknownOptions ()
+method, below.
+
+Normally this method is not called directly,
+but is called indirectly by passing the Options object to
the Environment() function:
.ES
are executed as Python scripts,
and the values of (global) Python variables set in the file
are added to the construction environment.
+
Example:
.ES
CC = 'my_cc'
.EE
+.TP
+.RI UnknownOptions( )
+Returns a dictionary containing any
+variables that were specified
+either in the files or the dictionary
+with which the Options object was intialized,
+but for which the Options object was
+not configured.
+
+.ES
+env = Environment(options=opts)
+for key, value in opts.UnknownOptions():
+ print "unknown variable: %s=%s" % (key, value)
+.EE
+
.TP
.RI Save( filename ", " env )
This saves the currently set options into a script file named
(for example, 'liba.a' on POSIX systems,
'a.lib' on Windows).
-.SS Customizing contruction variables from the command line.
+.SS Customizing construction variables from the command line.
The following would allow the C compiler to be specified on the command
line or in the file custom.py.
<!ENTITY Command "<function>Command</function>">
<!ENTITY Configure "<function>Configure</function>">
<!ENTITY Copy "<function>Copy</function>">
+<!ENTITY Decider "<function>Decider</function>">
<!ENTITY Default "<function>Default</function>">
<!ENTITY DefaultRules "<function>DefaultRules</function>">
<!ENTITY Delete "<function>Delete</function>">
The drawback is that the build can behave
differently if it's run by people with
different &PATH; values in their environment--for example,
- both the <literal>/bin</literal> and
+ if both the <literal>/bin</literal> and
<literal>/usr/local/bin</literal> directories
have different &cc; commands,
then which one will be used to compile programs
The drawback is that the build can behave
differently if it's run by people with
different &PATH; values in their environment--for example,
- both the <literal>/bin</literal> and
+ if both the <literal>/bin</literal> and
<literal>/usr/local/bin</literal> directories
have different &cc; commands,
then which one will be used to compile programs
<para>
- To be able use both our own defined &Builder; objects
+ To be able to use both our own defined &Builder; objects
and the default &Builder; objects in the same &consenv;,
you can either add to the &cv-BUILDERS; variable
using the &Append; function:
<para>
- To be able use both our own defined &Builder; objects
+ To be able to use both our own defined &Builder; objects
and the default &Builder; objects in the same &consenv;,
you can either add to the &cv-BUILDERS; variable
using the &Append; function:
<para>
If, for example,
- and you're using a POSIX shell that's
+ you're using a POSIX shell that's
compatible with the Bourne shell,
and you always want &SCons; to use the
<literal>-Q</literal> option,
<para>
- Windows users may typically want to set this
+ Windows users may typically want to set the
&SCONSFLAGS; in the appropriate tab of the
<literal>System Properties</literal> window.
to control an expected path name.
If, for example, you need to
define a variable in the preprocessor
- that control the location of a
+ that controls the location of a
configuration file:
</para>
</para>
</section>
+
+ <!--
+
+ AddOption() function for things like - -prefix=, - -force
+
+ -->
<para>
If, for example,
- and you're using a POSIX shell that's
+ you're using a POSIX shell that's
compatible with the Bourne shell,
and you always want &SCons; to use the
<literal>-Q</literal> option,
<para>
- Windows users may typically want to set this
+ Windows users may typically want to set the
&SCONSFLAGS; in the appropriate tab of the
<literal>System Properties</literal> window.
to control an expected path name.
If, for example, you need to
define a variable in the preprocessor
- that control the location of a
+ that controls the location of a
configuration file:
</para>
</para>
</section>
+
+ <!--
+
+ AddOption() function for things like - -prefix=, - -force
+
+ -->
<para>
So far we've seen how &SCons; handles one-time builds.
- But the real point of a build tool like &SCons;
+ But one of the main functions of a build tool like &SCons;
is to rebuild only the necessary things
when source files change--or, put another way,
&SCons; should <emphasis>not</emphasis>
waste time rebuilding things that have already been built.
- You can see this at work simply be re-invoking &SCons;
+ You can see this at work simply by re-invoking &SCons;
after building our simple &hello; example:
</para>
</para>
<section>
- <title>Deciding When a Source File Has Changed: the &SourceSignatures; Function</title>
+ <title>Deciding When an Input File Has Changed: the &Decider; Function</title>
<para>
- The other side of avoiding unnecessary rebuilds
+ Another aspect of avoiding unnecessary rebuilds
is the fundamental build tool behavior
of <emphasis>rebuilding</emphasis>
- things when a source file changes,
+ things when an input file changes,
so that the built software is up to date.
- &SCons; keeps track of this through a
- &signature; for each source file,
- and allows you to configure
- whether you want to use the source
- file contents or the modification time (timestamp)
- as the signature.
+ By default,
+ &SCons; keeps track of this through an
+ MD5 &signature;, or checksum, of the contents of each file,
+ although you can easily configure
+ &SCons; to use the
+ modification times (or time stamps)
+ instead.
+ You can even specify your own Python function
+ for deciding if an input file has changed.
</para>
<section>
- <title>MD5 Source File Signatures</title>
+ <title>Using MD5 Signatures to Decide if a File Has Changed</title>
<para>
By default,
- &SCons; keeps track of whether a source file has changed
- based on the file's contents,
- not the modification time.
+ &SCons; keeps track of whether a file has changed
+ based on an MD5 checksum of the file's contents,
+ not the file's modification time.
This means that you may be surprised by the
default &SCons; behavior if you are used to the
&Make; convention of forcing
Note that you can, if you wish,
specify this default behavior
(MD5 signatures) explicitly
- using the &SourceSignatures; function as follows:
+ using the &Decider; function as follows:
</para>
<sconstruct>
Program('hello.c')
- SourceSignatures('MD5')
+ Decider('MD5')
</sconstruct>
+ <para>
+
+ You can also use the string <literal>'content'</literal>
+ as a synonym for <literal>'MD5'</literal>
+ when calling the &Decider; function.
+
+ </para>
+
+ <section>
+ <title>Ramifications of Using MD5 Signatures</title>
+
+ <para>
+
+ Using MD5 Signatures to decide if an input file has changed
+ has one surprising benefit:
+ if a source file has been changed
+ in such a way that the contents of the
+ rebuilt target file(s)
+ will be exactly the same as the last time
+ the file was built,
+ then any "downstream" target files
+ that depend on the rebuilt-but-not-changed target
+ file actually need not be rebuilt.
+
+ </para>
+
+ <para>
+
+ So if, for example,
+ a user were to only change a comment in a &hello_c; file,
+ then the rebuilt &hello_o; file
+ would be exactly the same as the one previously built
+ (assuming the compiler doesn't put any build-specific
+ information in the object file).
+ &SCons; would then realize that it would not
+ need to rebuild the &hello; program as follows:
+
+ </para>
+
+ <scons_output example="ex1" os="posix">
+ <scons_output_command>scons -Q hello</scons_output_command>
+ <scons_output_command output=" [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command>
+ <scons_output_command>scons -Q hello</scons_output_command>
+ </scons_output>
+
+ <para>
+
+ In essence, &SCons;
+ "short-circuits" any dependent builds
+ when it realizes that a target file
+ has been rebuilt to exactly the same file as the last build.
+ This does take some extra processing time
+ to read the contents of the target (&hello_o;) file,
+ but often saves time when the rebuild that was avoided
+ would have been time-consuming and expensive.
+
+ </para>
+
+ </section>
+
</section>
<section>
- <title>Source File Time Stamps</title>
+ <title>Using Time Stamps to Decide If a File Has Changed</title>
<para>
If you prefer, you can
configure &SCons; to use the modification time
- of source files,
- not the file contents,
- when deciding if something needs to be rebuilt.
- To do this, call the &SourceSignatures;
+ of a file, not the file contents,
+ when deciding if a target needs to be rebuilt.
+ &SCons; gives you two ways to use time stamps
+ to decide if an input file has changed
+ since the last time a target has been built.
+
+ </para>
+
+ <para>
+
+ The most familiar way to use time stamps
+ is the way &Make; does:
+ that is, have &SCons; decide
+ and target must be rebuilt if
+ if a source file's modification time is
+ <emphasis>newer</emphasis>
+ than the target file.
+ To do this, call the &Decider;
function as follows:
</para>
- <scons_example name="ex2">
+ <scons_example name="newer">
<file name="SConstruct" printme="1">
Program('hello.c')
- SourceSignatures('timestamp')
+ Decider('timestamp-newer')
</file>
<file name="hello.c">
int main() { printf("Hello, world!\n"); }
</para>
- <scons_output example="ex2" os="posix">
+ <scons_output example="newer" os="posix">
<scons_output_command>scons -Q hello</scons_output_command>
<scons_output_command>touch hello.c</scons_output_command>
<scons_output_command>scons -Q hello</scons_output_command>
</scons_output>
- </section>
+ <para>
- </section>
+ And, in fact, because this behavior is the same
+ as the behavior of &Make;,
+ you can also use the string <literal>'make'</literal>
+ as a synonym for <literal>'timestamp-newer'</literal>
+ when calling the &Decider; function:
- <section>
- <title>Deciding When a Target File Has Changed: the &TargetSignatures; Function</title>
+ </para>
- <para>
+ <sconstruct>
+ Program('hello.c')
+ Decider('make')
+ </sconstruct>
- As you've just seen,
- &SCons; uses signatures to decide whether a
- target file is up to date or must be rebuilt.
- When a target file depends on another target file,
- &SCons; allows you to configure separately
- how the signatures of "intermediate" target files
- are used when deciding if a dependent target file
- must be rebuilt.
+ <para>
- </para>
+ One drawback to using times stamps exactly like &Make;
+ is that if an input file's modification time suddenly
+ becomes <emphasis>older</emphasis> than a target file,
+ the target file will not be rebuilt.
+ This can happen if an old copy of a source file is restored
+ from a backup archive, for example.
+ The contents of the restored file will likely be different
+ than they were the last time a dependent target was built,
+ but the target won't be rebuilt
+ because the modification time of the source file
+ is not newer than the target.
- <section>
- <title>Build Signatures</title>
+ </para>
<para>
- Modifying a source file
- will cause not only its direct target file to be rebuilt,
- but also the target file(s)
- that depend on that direct target file.
- In our example,
- changing the contents of the &hello_c; file causes
- the &hello_o; file to be rebuilt,
- which in turn causes the
- &hello; program to be rebuilt:
+ Because &SCons; actually stores information
+ about the source files' time stamps whenever a target is built,
+ it can handle this situation by checking for
+ an exact match of the source file time stamp,
+ instead of just whether or not the source file
+ is newer than the target file.
+ To do this, specify the argument
+ <literal>'timestamp-match'</literal>
+ when calling the &Decider; function:
</para>
- <scons_output example="ex1" os="posix">
+ <scons_example name="match">
+ <file name="SConstruct" printme="1">
+ Program('hello.c')
+ Decider('timestamp-match')
+ </file>
+ <file name="hello.c">
+ int main() { printf("Hello, world!\n"); }
+ </file>
+ </scons_example>
+
+ <para>
+
+ When configured this way,
+ &SCons; will rebuild a target whenever
+ a source file's modification time has changed.
+ So if we use the <literal>touch -t</literal>
+ option to change the modification time of
+ &hello_c; to an old date (January 1, 1989),
+ &SCons; will still rebuild the target file:
+
+ </para>
+
+ <scons_output example="match" os="posix">
<scons_output_command>scons -Q hello</scons_output_command>
- <scons_output_command output=" [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command>
+ <scons_output_command>touch -t 198901010000 hello.c</scons_output_command>
<scons_output_command>scons -Q hello</scons_output_command>
</scons_output>
<para>
- What's not obvious, though,
- is that &SCons; internally handles the signature of
- the target file(s)
- (&hello_o; in the above example)
- differently from the signature of the source file
- (&hello_c;).
- By default,
- &SCons; tracks whether a target file must be rebuilt
- by using a &buildsignature;
- that consists of the combined
- signatures of all the files
- that go into making the target file.
- This is efficient because
- the accumulated signatures
- actually give &SCons; all of the
- information it needs
- to decide if the target file is out of date.
+ In general, the only reason to prefer
+ <literal>timestamp-newer</literal>
+ instead of
+ <literal>timestamp-match</literal>,
+ would be if you have some specific reason
+ to require this &Make;-like behavior of
+ not rebuilding a target when an otherwise-modified
+ source file is older.
</para>
+ </section>
+
+ <section>
+ <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title>
+
<para>
- If you wish, you can
- specify this default behavior
- (build signatures) explicitly
- using the &TargetSignatures; function:
+ As a performance enhancement,
+ &SCons; provides a way to use
+ MD5 checksums of file contents
+ but to only read the contents
+ whenever the file's timestamp has changed.
+ To do this, call the &Decider;
+ function with <literal>'MD5-timestamp'</literal>
+ argument as follows:
</para>
- <sconstruct>
+ <scons_example name="MD5-timestamp">
+ <file name="SConstruct" printme="1">
Program('hello.c')
- TargetSignatures('build')
- </sconstruct>
+ Decider('MD5-timestamp')
+ </file>
+ <file name="hello.c">
+ int main() { printf("Hello, world!\n"); }
+ </file>
+ </scons_example>
+
+ <para>
+
+ So configured, &SCons will still behave like
+ it does when using <literal>Decider('MD5')</literal>:
+
+ </para>
+
+ <scons_output example="MD5-timestamp" os="posix">
+ <scons_output_command>scons -Q hello</scons_output_command>
+ <scons_output_command>touch hello.c</scons_output_command>
+ <scons_output_command>scons -Q hello</scons_output_command>
+ <scons_output_command output=" [CHANGE THE CONTENTS OF hello.c]">edit hello.c</scons_output_command>
+ <scons_output_command>scons -Q hello</scons_output_command>
+ </scons_output>
+
+ <para>
+
+ However, the second call to &SCons; in the above output,
+ when the build is up-to-date,
+ will have been performed by simply looking at the
+ modification time of the &hello_c; file,
+ not by opening it and performing
+ an MD5 checksum calcuation on its contents.
+ This can significantly speed up many up-to-date builds.
+
+ </para>
+
+ <para>
+
+ The only drawback to using
+ <literal>Decider('MD5-timestamp')</literal>
+ is that &SCons; will <emphasis>not</emphasis>
+ rebuild a target file if a source file was modified
+ within one second of the last time &SCons; built the file.
+ While most developers are programming,
+ this isn't a problem in practice,
+ since it's unlikely that someone will have built
+ and then thought quickly enought to make a substantive
+ change to a source file within one second.
+ Certain build scripts or
+ continuous integration tools may, however,
+ rely on the ability to applying changes to files
+ automatically and then rebuild as quickly as possible,
+ in which case use of
+ <literal>Decider('MD5-timestamp')</literal>
+ may not be appropriate.
+
+ </para>
</section>
<section>
- <title>File Contents</title>
+ <title>Writing Your Own Custom &Decider; Function</title>
<para>
- Sometimes a source file can be changed
- in such a way that the contents of the
- rebuilt target file(s)
- will be exactly the same as the last time
- the file was built.
- If so, then any other target files
- that depend on such a built-but-not-changed target
- file actually need not be rebuilt.
- You can make &SCons;
- realize that it does not need to rebuild
- a dependent target file in this situation
- using the &TargetSignatures; function as follows:
+ The different string values that we've passed to
+ the &Decider; function are essentially used by &SCons;
+ to pick one of several specific internal functions
+ that implement various ways of deciding if a dependency
+ (usually a source file)
+ has changed since a target file has been built.
+ As it turns out,
+ you can also supply your own function
+ to decide if a dependency has changed.
</para>
- <scons_example name="ex3">
+ <para>
+
+ For example, suppose we have an input file
+ that contains a lot of data,
+ in some specific regular format,
+ that is used to rebuild a lot of different target files,
+ but each target file really only depends on
+ one particular section of the input file.
+ We'd like to have each target file depend on
+ only its section of the input file.
+ However, since the input file may contain a lot of data,
+ we only want to open the input file if its timestamp has changed.
+ This could done with a custom
+ &Decider; function that might look something like this:
+
+ </para>
+
+ <scons_example name="function">
<file name="SConstruct" printme="1">
Program('hello.c')
- TargetSignatures('content')
+ def decide_if_changed(dependency, target, prev_ni):
+ if self.get_timestamp() != prev_ni.timestamp:
+ dep = str(dependency)
+ tgt = str(target)
+ if specific_part_of_file_has_changed(dep, tgt):
+ return True
+ return False
+ Decider(decide_if_changed)
</file>
<file name="hello.c">
int main() { printf("Hello, world!\n"); }
<para>
- So if, for example,
- a user were to only change a comment in a C file,
- then the rebuilt &hello_o; file
- would be exactly the same as the one previously built
- (assuming the compiler doesn't put any build-specific
- information in the object file).
- &SCons; would then realize that it would not
- need to rebuild the &hello; program as follows:
+ Note that in the function definition,
+ the <literal>dependency</literal>
+ (input file) is the first argument,
+ and then the <literal>target</literal>.
+ Both of these are passed to the functions as
+ SCons &Node; objects,
+ which we convert to strings using the Python
+ <function>str()</function>.
+ The third argument, <literal>prev_ni</literal>,
+ is an object that holds the
+ signature or timestamp information
+ that was recorded about the dependency
+ the last time the target was built.
</para>
- <scons_output example="ex3" os="posix">
- <scons_output_command>scons -Q hello</scons_output_command>
- <scons_output_command output=" [CHANGE A COMMENT IN hello.c]" edit="STRIP CCCOM line">edit hello.c</scons_output_command>
- <scons_output_command>scons -Q hello</scons_output_command>
+ <para>
+
+ Note that ignoring some of the arguments
+ in your custom &Decider; function
+ is a perfectly normal thing to do,
+ if they don't impact the way you want to
+ decide if the dependency file has changed.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>Mixing Different Ways of Deciding If a File Has Changed</title>
+
+ <para>
+
+ The previous examples have all demonstrated calling
+ the global &Decider; function
+ to configure all dependency decisions that &SCons; makes.
+ Sometimes, however, you want to be able to configure
+ different decision-making for different targets.
+ When that's necessary, you can use the
+ <function>env.Decider</function>
+ method to affect only the configuration
+ decisions for targets built with a
+ specific construction environment.
+
+ </para>
+
+ <para>
+
+ For example, if we arbitrarily want to build
+ one program using MD5 checkums
+ and another use file modification times
+ from the same source
+ we might configure it this way:
+
+ </para>
+
+ <scons_example name="mixing">
+ <file name="SConstruct" printme="1">
+ env1 = Environment(CPPPATH = ['.'])
+ env2 = env1.Clone()
+ env2.Decider('timestamp-match')
+ env1.Program('prog-MD5', 'program1.c')
+ env2.Program('prog-timestamp', 'program2.c')
+ </file>
+ <file name="program1.c">
+ #include "inc.h"
+ int main() { printf("Hello, world!\n"); }
+ </file>
+ <file name="program2.c">
+ #include "inc.h"
+ int main() { printf("Hello, world!\n"); }
+ </file>
+ <file name="inc.h">
+ #define INC 1
+ </file>
+ </scons_example>
+
+ <para>
+
+ If both of the programs include the same
+ <filename>inc.h</filename> file,
+ then updating the modification time of
+ <filename>inc.h</filename>
+ (using the &touch; command)
+ will cause only <filename>prog-timestamp</filename>
+ to be rebuilt:
+
+ </para>
+
+ <scons_output example="mixing" os="posix">
+ <scons_output_command>scons -Q</scons_output_command>
+ <scons_output_command>touch inc.h</scons_output_command>
+ <scons_output_command>scons -Q</scons_output_command>
</scons_output>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Older Functions for Deciding When an Input File Has Changed</title>
+
+ <para>
+
+ &SCons; still supports two functions that used to be the
+ primary methods for configuring the
+ decision about whether or not an input file has changed.
+ Although they're not officially deprecated yet,
+ their use is discouraged,
+ mainly because they rely on a somewhat
+ confusing distinction between how
+ source files and target files are handled.
+ These functions are documented here mainly in case you
+ encounter them in existing &SConscript; files.
+
+ </para>
+
+ <section>
+ <title>The &SourceSignatures; Function</title>
+
<para>
- In essence, &SCons; has
- "short-circuited" any dependent builds
- when it realizes that a target file
- has been rebuilt to exactly the same file as the last build.
- So configured,
- &SCons; does take some extra processing time
- to scan the contents of the target (&hello_o;) file,
- but this may save time
- if the rebuild that was avoided
- would have been very time-consuming and expensive.
+ The &SourceSignatures; function is fairly straightforward,
+ and supports two different argument values
+ to configure whether source file changes should be decided
+ using MD5 signatures:
+
+ </para>
+
+ <sconstruct>
+ Program('hello.c')
+ SourceSignatures('MD5')
+ </sconstruct>
+
+ <para>
+
+ Or using time stamps:
+
+ </para>
+
+ <sconstruct>
+ Program('hello.c')
+ SourceSignatures('timestamp')
+ </sconstruct>
+
+ <para>
+
+ These are roughly equivalent to specifying
+ <function>Decider('MD5')</function>
+ or
+ <function>Decider('timestamp-match')</function>,
+ respectively,
+ although it only affects how SCons makes
+ decisions about dependencies on
+ <emphasis>source</emphasis> files--that is,
+ files that are not built from any other files.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>The &TargetSignatures; Function</title>
+
+ <para>
+
+ The &TargetSignatures; function
+ specifies how &SCons; decides
+ when a target file has changed
+ <emphasis>when it is used as a
+ dependency of (input to) another target</emphasis>--that is,
+ the &TargetSignatures; function configures
+ how the signatures of "intermediate" target files
+ are used when deciding if a "downstream" target file
+ must be rebuilt.
+ <footnote><para>
+ This easily-overlooked distinction between
+ how &SCons; decides if the target itself must be rebuilt
+ and how the target is then used to decide if a different
+ target must be rebuilt is one of the confusing
+ things that has led to the &TargetSignatures;
+ and &SourceSignatures; functions being
+ replaced by the simpler &Decider; function.
+ </para></footnote>
+
+ </para>
+
+ <para>
+
+ The &TargetSignatures; function supports the same
+ <literal>'MD5'</literal> and <literal>'timestamp'</literal>
+ argument values that are supported by the &SourceSignatures;,
+ with the same meanings, but applied to target files.
+ That is, in the example:
+
+ </para>
+
+ <sconstruct>
+ Program('hello.c')
+ TargetSignatures('MD5')
+ </sconstruct>
+
+ <para>
+
+ The MD5 checksum of the &hello_o; target file
+ will be used to decide if it has changed since the last
+ time the "downstream" &hello; target file was built.
+ And in the example:
+
+ </para>
+
+ <sconstruct>
+ Program('hello.c')
+ TargetSignatures('timestamp')
+ </sconstruct>
+
+ <para>
+
+ The modification time of the &hello_o; target file
+ will be used to decide if it has changed since the last
+ time the "downstream" &hello; target file was built.
+
+ </para>
+
+ <para>
+
+ The &TargetSignatures; function supports
+ two additional argument values:
+ <literal>'source'</literal> and <literal>'build'</literal>.
+ The <literal>'source'</literal> argument
+ specifies that decisions involving
+ whether target files have changed
+ since a previous build
+ should use the same behavior
+ for the decisions configured for source files
+ (using the &SourceSignatures; function).
+ So in the example:
+
+ </para>
+
+ <sconstruct>
+ Program('hello.c')
+ TargetSignatures('source')
+ SourceSignatures('timestamp')
+ </sconstruct>
+
+ <para>
+
+ All files, both targets and sources,
+ will use modification times
+ when deciding if an input file
+ has changed since the last
+ time a target was built.
+
+ </para>
+
+ <para>
+
+ Lastly, the <literal>'build'</literal> argument
+ specifies that &SCons; should examine
+ the build status of a target file
+ and always rebuild a "downstream" target
+ if the target file was itself rebuilt,
+ without re-examining the contents or timestamp
+ of the newly-built target file.
+ If the target file was not rebuilt during
+ this &scons; invocation,
+ then the target file will be examined
+ the same way as configured by
+ the &SourceSignature; call
+ to decide if it has changed.
+
+ </para>
+
+ <para>
+
+ This mimics the behavior of
+ <literal>build signatures</literal>
+ in earlier versions of &SCons;.
+ A &buildsignature; re-combined
+ signatures of all the input files
+ that went into making the target file,
+ so that the target file itself
+ did not need to have its contents read
+ to compute an MD5 signature.
+ This can improve performance for some configurations,
+ but is generally not as effective as using
+ <literal>Decider('MD5-timestamp')</literal>.
</para>
<para>
Now suppose that our "Hello, World!" program
- actually has a <literal>#include</literal> line
+ actually has an <literal>#include</literal> line
to include the &hello_h; file in the compilation:
</para>
- <scons_example name="ex4">
+ <scons_example name="include">
<file name="SConstruct">
Program('hello.c', CPPPATH = '.')
</file>
</para>
- <scons_example_file example="ex4" name="hello.h">
+ <scons_example_file example="include" name="hello.h">
</scons_example_file>
<para>
</para>
- <scons_example_file example="ex4" name="SConstruct">
+ <scons_example_file example="include" name="SConstruct">
</scons_example_file>
<para>
</para>
- <scons_output example="ex4" os="posix">
+ <scons_output example="include" os="posix">
<scons_output_command>scons -Q hello</scons_output_command>
<scons_output_command>scons -Q hello</scons_output_command>
<scons_output_command output=" [CHANGE THE CONTENTS OF hello.h]">edit hello.h</scons_output_command>
the &cv-CPPPATH; variable
may be a list of directories,
or a string separated by
- the system-specific path separate character
+ the system-specific path separation character
(':' on POSIX/Linux, ';' on Windows).
Either way, &SCons; creates the
right command-line options
</section>
<section>
- <title>Ignoring Dependencies: the &Ignore; Method</title>
+ <title>Explicit Dependencies: the &Depends; Function</title>
+
+ <para>
+
+ Sometimes a file depends on another file
+ that is not detected by an &SCons; scanner.
+ For this situation,
+ &SCons; allows you to specific explicitly that one file
+ depends on another file,
+ and must be rebuilt whenever that file changes.
+ This is specified using the &Depends; method:
+
+ </para>
+
+ <programlisting>
+ hello = Program('hello.c')
+ Depends(hello, 'other_file')
+ </programlisting>
+
+ <!-- XXX mention that you can use arrays for target and source? -->
+
+ <screen>
+ % <userinput>scons -Q hello</userinput>
+ cc -c hello.c -o hello.o
+ cc -o hello hello.o
+ % <userinput>scons -Q hello</userinput>
+ scons: `hello' is up to date.
+ % <userinput>edit other_file</userinput>
+ [CHANGE THE CONTENTS OF other_file]
+ % <userinput>scons -Q hello</userinput>
+ cc -c hello.c -o hello.o
+ cc -o hello hello.o
+ </screen>
+
+ </section>
+
+ <section>
+ <title>Ignoring Dependencies: the &Ignore; Function</title>
<para>
Now, the above example is a little contrived,
because it's hard to imagine a real-world situation
- where you wouldn't to rebuild &hello;
+ where you wouldn't want to rebuild &hello;
if the &hello_h; file changed.
A more realistic example
might be if the &hello;
</section>
<section>
- <title>Explicit Dependencies: the &Depends; Method</title>
-
- <para>
-
- On the other hand,
- sometimes a file depends on another file
- that is not detected by an &SCons; scanner.
- For this situation,
- &SCons; allows you to specific explicitly that one file
- depends on another file,
- and must be rebuilt whenever that file changes.
- This is specified using the &Depends; method:
-
- </para>
-
- <programlisting>
- hello = Program('hello.c')
- Depends(hello, 'other_file')
- </programlisting>
-
- <!-- XXX mention that you can use arrays for target and source? -->
-
- <screen>
- % <userinput>scons -Q hello</userinput>
- cc -c hello.c -o hello.o
- cc -o hello hello.o
- % <userinput>scons -Q hello</userinput>
- scons: `hello' is up to date.
- % <userinput>edit other_file</userinput>
- [CHANGE THE CONTENTS OF other_file]
- % <userinput>scons -Q hello</userinput>
- cc -c hello.c -o hello.o
- cc -o hello hello.o
- </screen>
-
- </section>
-
- <section>
- <title>The &AlwaysBuild; Method</title>
+ <title>The &AlwaysBuild; Function</title>
<para>
<para>
So far we've seen how &SCons; handles one-time builds.
- But the real point of a build tool like &SCons;
+ But one of the main functions of a build tool like &SCons;
is to rebuild only the necessary things
when source files change--or, put another way,
&SCons; should <emphasis>not</emphasis>
waste time rebuilding things that have already been built.
- You can see this at work simply be re-invoking &SCons;
+ You can see this at work simply by re-invoking &SCons;
after building our simple &hello; example:
</para>
</para>
<section>
- <title>Deciding When a Source File Has Changed: the &SourceSignatures; Function</title>
+ <title>Deciding When an Input File Has Changed: the &Decider; Function</title>
<para>
- The other side of avoiding unnecessary rebuilds
+ Another aspect of avoiding unnecessary rebuilds
is the fundamental build tool behavior
of <emphasis>rebuilding</emphasis>
- things when a source file changes,
+ things when an input file changes,
so that the built software is up to date.
- &SCons; keeps track of this through a
- &signature; for each source file,
- and allows you to configure
- whether you want to use the source
- file contents or the modification time (timestamp)
- as the signature.
+ By default,
+ &SCons; keeps track of this through an
+ MD5 &signature;, or checksum, of the contents of each file,
+ although you can easily configure
+ &SCons; to use the
+ modification times (or time stamps)
+ instead.
+ You can even specify your own Python function
+ for deciding if an input file has changed.
</para>
<section>
- <title>MD5 Source File Signatures</title>
+ <title>Using MD5 Signatures to Decide if a File Has Changed</title>
<para>
By default,
- &SCons; keeps track of whether a source file has changed
- based on the file's contents,
- not the modification time.
+ &SCons; keeps track of whether a file has changed
+ based on an MD5 checksum of the file's contents,
+ not the file's modification time.
This means that you may be surprised by the
default &SCons; behavior if you are used to the
&Make; convention of forcing
Note that you can, if you wish,
specify this default behavior
(MD5 signatures) explicitly
- using the &SourceSignatures; function as follows:
+ using the &Decider; function as follows:
</para>
<programlisting>
Program('hello.c')
- SourceSignatures('MD5')
+ Decider('MD5')
</programlisting>
+ <para>
+
+ You can also use the string <literal>'content'</literal>
+ as a synonym for <literal>'MD5'</literal>
+ when calling the &Decider; function.
+
+ </para>
+
+ <section>
+ <title>Ramifications of Using MD5 Signatures</title>
+
+ <para>
+
+ Using MD5 Signatures to decide if an input file has changed
+ has one surprising benefit:
+ if a source file has been changed
+ in such a way that the contents of the
+ rebuilt target file(s)
+ will be exactly the same as the last time
+ the file was built,
+ then any "downstream" target files
+ that depend on the rebuilt-but-not-changed target
+ file actually need not be rebuilt.
+
+ </para>
+
+ <para>
+
+ So if, for example,
+ a user were to only change a comment in a &hello_c; file,
+ then the rebuilt &hello_o; file
+ would be exactly the same as the one previously built
+ (assuming the compiler doesn't put any build-specific
+ information in the object file).
+ &SCons; would then realize that it would not
+ need to rebuild the &hello; program as follows:
+
+ </para>
+
+ <screen>
+ % <userinput>scons -Q hello</userinput>
+ cc -o hello.o -c hello.c
+ cc -o hello hello.o
+ % <userinput>edit hello.c</userinput>
+ [CHANGE A COMMENT IN hello.c]
+ % <userinput>scons -Q hello</userinput>
+ cc -o hello.o -c hello.c
+ scons: `hello' is up to date.
+ </screen>
+
+ <para>
+
+ In essence, &SCons;
+ "short-circuits" any dependent builds
+ when it realizes that a target file
+ has been rebuilt to exactly the same file as the last build.
+ This does take some extra processing time
+ to read the contents of the target (&hello_o;) file,
+ but often saves time when the rebuild that was avoided
+ would have been time-consuming and expensive.
+
+ </para>
+
+ </section>
+
</section>
<section>
- <title>Source File Time Stamps</title>
+ <title>Using Time Stamps to Decide If a File Has Changed</title>
<para>
If you prefer, you can
configure &SCons; to use the modification time
- of source files,
- not the file contents,
- when deciding if something needs to be rebuilt.
- To do this, call the &SourceSignatures;
+ of a file, not the file contents,
+ when deciding if a target needs to be rebuilt.
+ &SCons; gives you two ways to use time stamps
+ to decide if an input file has changed
+ since the last time a target has been built.
+
+ </para>
+
+ <para>
+
+ The most familiar way to use time stamps
+ is the way &Make; does:
+ that is, have &SCons; decide
+ and target must be rebuilt if
+ if a source file's modification time is
+ <emphasis>newer</emphasis>
+ than the target file.
+ To do this, call the &Decider;
function as follows:
</para>
<programlisting>
Program('hello.c')
- SourceSignatures('timestamp')
+ Decider('timestamp-newer')
</programlisting>
<para>
cc -o hello hello.o
</screen>
- </section>
+ <para>
- </section>
+ And, in fact, because this behavior is the same
+ as the behavior of &Make;,
+ you can also use the string <literal>'make'</literal>
+ as a synonym for <literal>'timestamp-newer'</literal>
+ when calling the &Decider; function:
- <section>
- <title>Deciding When a Target File Has Changed: the &TargetSignatures; Function</title>
+ </para>
- <para>
+ <programlisting>
+ Program('hello.c')
+ Decider('make')
+ </programlisting>
- As you've just seen,
- &SCons; uses signatures to decide whether a
- target file is up to date or must be rebuilt.
- When a target file depends on another target file,
- &SCons; allows you to configure separately
- how the signatures of "intermediate" target files
- are used when deciding if a dependent target file
- must be rebuilt.
+ <para>
- </para>
+ One drawback to using times stamps exactly like &Make;
+ is that if an input file's modification time suddenly
+ becomes <emphasis>older</emphasis> than a target file,
+ the target file will not be rebuilt.
+ This can happen if an old copy of a source file is restored
+ from a backup archive, for example.
+ The contents of the restored file will likely be different
+ than they were the last time a dependent target was built,
+ but the target won't be rebuilt
+ because the modification time of the source file
+ is not newer than the target.
+
+ </para>
+
+ <para>
+
+ Because &SCons; actually stores information
+ about the source files' time stamps whenever a target is built,
+ it can handle this situation by checking for
+ an exact match of the source file time stamp,
+ instead of just whether or not the source file
+ is newer than the target file.
+ To do this, specify the argument
+ <literal>'timestamp-match'</literal>
+ when calling the &Decider; function:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ Decider('timestamp-match')
+ </programlisting>
+
+ <para>
+
+ When configured this way,
+ &SCons; will rebuild a target whenever
+ a source file's modification time has changed.
+ So if we use the <literal>touch -t</literal>
+ option to change the modification time of
+ &hello_c; to an old date (January 1, 1989),
+ &SCons; will still rebuild the target file:
+
+ </para>
+
+ <screen>
+ % <userinput>scons -Q hello</userinput>
+ cc -o hello.o -c hello.c
+ cc -o hello hello.o
+ % <userinput>touch -t 198901010000 hello.c</userinput>
+ % <userinput>scons -Q hello</userinput>
+ cc -o hello.o -c hello.c
+ scons: `hello' is up to date.
+ </screen>
+
+ <para>
+
+ In general, the only reason to prefer
+ <literal>timestamp-newer</literal>
+ instead of
+ <literal>timestamp-match</literal>,
+ would be if you have some specific reason
+ to require this &Make;-like behavior of
+ not rebuilding a target when an otherwise-modified
+ source file is older.
+
+ </para>
+
+ </section>
<section>
- <title>Build Signatures</title>
+ <title>Deciding If a File Has Changed Using Both MD Signatures and Time Stamps</title>
+
+ <para>
+
+ As a performance enhancement,
+ &SCons; provides a way to use
+ MD5 checksums of file contents
+ but to only read the contents
+ whenever the file's timestamp has changed.
+ To do this, call the &Decider;
+ function with <literal>'MD5-timestamp'</literal>
+ argument as follows:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ Decider('MD5-timestamp')
+ </programlisting>
<para>
- Modifying a source file
- will cause not only its direct target file to be rebuilt,
- but also the target file(s)
- that depend on that direct target file.
- In our example,
- changing the contents of the &hello_c; file causes
- the &hello_o; file to be rebuilt,
- which in turn causes the
- &hello; program to be rebuilt:
+ So configured, &SCons; will still behave like
+ it does when using <literal>Decider('MD5')</literal>:
</para>
% <userinput>scons -Q hello</userinput>
cc -o hello.o -c hello.c
cc -o hello hello.o
+ % <userinput>touch hello.c</userinput>
+ % <userinput>scons -Q hello</userinput>
+ scons: `hello' is up to date.
% <userinput>edit hello.c</userinput>
[CHANGE THE CONTENTS OF hello.c]
% <userinput>scons -Q hello</userinput>
- cc -o hello.o -c hello.c
- cc -o hello hello.o
+ scons: `hello' is up to date.
</screen>
<para>
- What's not obvious, though,
- is that &SCons; internally handles the signature of
- the target file(s)
- (&hello_o; in the above example)
- differently from the signature of the source file
- (&hello_c;).
- By default,
- &SCons; tracks whether a target file must be rebuilt
- by using a &buildsignature;
- that consists of the combined
- signatures of all the files
- that go into making the target file.
- This is efficient because
- the accumulated signatures
- actually give &SCons; all of the
- information it needs
- to decide if the target file is out of date.
+ However, the second call to &SCons; in the above output,
+ when the build is up-to-date,
+ will have been performed by simply looking at the
+ modification time of the &hello_c; file,
+ not by opening it and performing
+ an MD5 checksum calcuation on its contents.
+ This can significantly speed up many up-to-date builds.
</para>
<para>
- If you wish, you can
- specify this default behavior
- (build signatures) explicitly
- using the &TargetSignatures; function:
+ The only drawback to using
+ <literal>Decider('MD5-timestamp')</literal>
+ is that &SCons; will <emphasis>not</emphasis>
+ rebuild a target file if a source file was modified
+ within one second of the last time &SCons; built the file.
+ While most developers are programming,
+ this isn't a problem in practice,
+ since it's unlikely that someone will have built
+ and then thought quickly enought to make a substantive
+ change to a source file within one second.
+ Certain build scripts or
+ continuous integration tools may, however,
+ rely on the ability to applying changes to files
+ automatically and then rebuild as quickly as possible,
+ in which case use of
+ <literal>Decider('MD5-timestamp')</literal>
+ may not be appropriate.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>Writing Your Own Custom &Decider; Function</title>
+
+ <para>
+
+ The different string values that we've passed to
+ the &Decider; function are essentially used by &SCons;
+ to pick one of several specific internal functions
+ that implement various ways of deciding if a dependency
+ (usually a source file)
+ has changed since a target file has been built.
+ As it turns out,
+ you can also supply your own function
+ to decide if a dependency has changed.
+
+ </para>
+
+ <para>
+
+ For example, suppose we have an input file
+ that contains a lot of data,
+ in some specific regular format,
+ that is used to rebuild a lot of different target files,
+ but each target file really only depends on
+ one particular section of the input file.
+ We'd like to have each target file depend on
+ only its section of the input file.
+ However, since the input file may contain a lot of data,
+ we only want to open the input file if its timestamp has changed.
+ This could done with a custom
+ &Decider; function that might look something like this:
</para>
<programlisting>
Program('hello.c')
- TargetSignatures('build')
+ def decide_if_changed(dependency, target, prev_ni):
+ if self.get_timestamp() != prev_ni.timestamp:
+ dep = str(dependency)
+ tgt = str(target)
+ if specific_part_of_file_has_changed(dep, tgt):
+ return True
+ return False
+ Decider(decide_if_changed)
</programlisting>
+ <para>
+
+ Note that in the function definition,
+ the <literal>dependency</literal>
+ (input file) is the first argument,
+ and then the <literal>target</literal>.
+ Both of these are passed to the functions as
+ SCons &Node; objects,
+ which we convert to strings using the Python
+ <function>str()</function>.
+ The third argument, <literal>prev_ni</literal>,
+ is an object that holds the
+ signature or timestamp information
+ that was recorded about the dependency
+ the last time the target was built.
+
+ </para>
+
+ <para>
+
+ Note that ignoring some of the arguments
+ in your custom &Decider; function
+ is a perfectly normal thing to do,
+ if they don't impact the way you want to
+ decide if the dependency file has changed.
+
+ </para>
+
</section>
<section>
- <title>File Contents</title>
+ <title>Mixing Different Ways of Deciding If a File Has Changed</title>
+
+ <para>
+
+ The previous examples have all demonstrated calling
+ the global &Decider; function
+ to configure all dependency decisions that &SCons; makes.
+ Sometimes, however, you want to be able to configure
+ different decision-making for different targets.
+ When that's necessary, you can use the
+ <function>env.Decider</function>
+ method to affect only the configuration
+ decisions for targets built with a
+ specific construction environment.
+
+ </para>
<para>
- Sometimes a source file can be changed
- in such a way that the contents of the
- rebuilt target file(s)
- will be exactly the same as the last time
- the file was built.
- If so, then any other target files
- that depend on such a built-but-not-changed target
- file actually need not be rebuilt.
- You can make &SCons;
- realize that it does not need to rebuild
- a dependent target file in this situation
- using the &TargetSignatures; function as follows:
+ For example, if we arbitrarily want to build
+ one program using MD5 checkums
+ and another use file modification times
+ from the same source
+ we might configure it this way:
</para>
<programlisting>
- Program('hello.c')
- TargetSignatures('content')
+ env1 = Environment(CPPPATH = ['.'])
+ env2 = env1.Clone()
+ env2.Decider('timestamp-match')
+ env1.Program('prog-MD5', 'program1.c')
+ env2.Program('prog-timestamp', 'program2.c')
</programlisting>
<para>
- So if, for example,
- a user were to only change a comment in a C file,
- then the rebuilt &hello_o; file
- would be exactly the same as the one previously built
- (assuming the compiler doesn't put any build-specific
- information in the object file).
- &SCons; would then realize that it would not
- need to rebuild the &hello; program as follows:
+ If both of the programs include the same
+ <filename>inc.h</filename> file,
+ then updating the modification time of
+ <filename>inc.h</filename>
+ (using the &touch; command)
+ will cause only <filename>prog-timestamp</filename>
+ to be rebuilt:
</para>
<screen>
- % <userinput>scons -Q hello</userinput>
- cc -o hello.o -c hello.c
- cc -o hello hello.o
- % <userinput>edit hello.c</userinput>
- [CHANGE A COMMENT IN hello.c]
- % <userinput>scons -Q hello</userinput>
- cc -o hello.o -c hello.c
- scons: `hello' is up to date.
+ % <userinput>scons -Q</userinput>
+ cc -o program1.o -c -I. program1.c
+ cc -o prog-MD5 program1.o
+ cc -o program2.o -c -I. program2.c
+ cc -o prog-timestamp program2.o
+ % <userinput>touch inc.h</userinput>
+ % <userinput>scons -Q</userinput>
+ cc -o program2.o -c -I. program2.c
+ cc -o prog-timestamp program2.o
</screen>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Older Functions for Deciding When an Input File Has Changed</title>
+
+ <para>
+
+ &SCons; still supports two functions that used to be the
+ primary methods for configuring the
+ decision about whether or not an input file has changed.
+ Although they're not officially deprecated yet,
+ their use is discouraged,
+ mainly because they rely on a somewhat
+ confusing distinction between how
+ source files and target files are handled.
+ These functions are documented here mainly in case you
+ encounter them in existing &SConscript; files.
+
+ </para>
+
+ <section>
+ <title>The &SourceSignatures; Function</title>
+
+ <para>
+
+ The &SourceSignatures; function is fairly straightforward,
+ and supports two different argument values
+ to configure whether source file changes should be decided
+ using MD5 signatures:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ SourceSignatures('MD5')
+ </programlisting>
+
+ <para>
+
+ Or using time stamps:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ SourceSignatures('timestamp')
+ </programlisting>
+
<para>
- In essence, &SCons; has
- "short-circuited" any dependent builds
- when it realizes that a target file
- has been rebuilt to exactly the same file as the last build.
- So configured,
- &SCons; does take some extra processing time
- to scan the contents of the target (&hello_o;) file,
- but this may save time
- if the rebuild that was avoided
- would have been very time-consuming and expensive.
+ These are roughly equivalent to specifying
+ <function>Decider('MD5')</function>
+ or
+ <function>Decider('timestamp-match')</function>,
+ respectively,
+ although it only affects how SCons makes
+ decisions about dependencies on
+ <emphasis>source</emphasis> files--that is,
+ files that are not built from any other files.
+
+ </para>
+
+ </section>
+
+ <section>
+ <title>The &TargetSignatures; Function</title>
+
+ <para>
+
+ The &TargetSignatures; function
+ specifies how &SCons; decides
+ when a target file has changed
+ <emphasis>when it is used as a
+ dependency of (input to) another target</emphasis>--that is,
+ the &TargetSignatures; function configures
+ how the signatures of "intermediate" target files
+ are used when deciding if a "downstream" target file
+ must be rebuilt.
+ <footnote><para>
+ This easily-overlooked distinction between
+ how &SCons; decides if the target itself must be rebuilt
+ and how the target is then used to decide if a different
+ target must be rebuilt is one of the confusing
+ things that has led to the &TargetSignatures;
+ and &SourceSignatures; functions being
+ replaced by the simpler &Decider; function.
+ </para></footnote>
+
+ </para>
+
+ <para>
+
+ The &TargetSignatures; function supports the same
+ <literal>'MD5'</literal> and <literal>'timestamp'</literal>
+ argument values that are supported by the &SourceSignatures;,
+ with the same meanings, but applied to target files.
+ That is, in the example:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ TargetSignatures('MD5')
+ </programlisting>
+
+ <para>
+
+ The MD5 checksum of the &hello_o; target file
+ will be used to decide if it has changed since the last
+ time the "downstream" &hello; target file was built.
+ And in the example:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ TargetSignatures('timestamp')
+ </programlisting>
+
+ <para>
+
+ The modification time of the &hello_o; target file
+ will be used to decide if it has changed since the last
+ time the "downstream" &hello; target file was built.
+
+ </para>
+
+ <para>
+
+ The &TargetSignatures; function supports
+ two additional argument values:
+ <literal>'source'</literal> and <literal>'build'</literal>.
+ The <literal>'source'</literal> argument
+ specifies that decisions involving
+ whether target files have changed
+ since a previous build
+ should use the same behavior
+ for the decisions configured for source files
+ (using the &SourceSignatures; function).
+ So in the example:
+
+ </para>
+
+ <programlisting>
+ Program('hello.c')
+ TargetSignatures('source')
+ SourceSignatures('timestamp')
+ </programlisting>
+
+ <para>
+
+ All files, both targets and sources,
+ will use modification times
+ when deciding if an input file
+ has changed since the last
+ time a target was built.
+
+ </para>
+
+ <para>
+
+ Lastly, the <literal>'build'</literal> argument
+ specifies that &SCons; should examine
+ the build status of a target file
+ and always rebuild a "downstream" target
+ if the target file was itself rebuilt,
+ without re-examining the contents or timestamp
+ of the newly-built target file.
+ If the target file was not rebuilt during
+ this &scons; invocation,
+ then the target file will be examined
+ the same way as configured by
+ the &SourceSignature; call
+ to decide if it has changed.
+
+ </para>
+
+ <para>
+
+ This mimics the behavior of
+ <literal>build signatures</literal>
+ in earlier versions of &SCons;.
+ A &buildsignature; re-combined
+ signatures of all the input files
+ that went into making the target file,
+ so that the target file itself
+ did not need to have its contents read
+ to compute an MD5 signature.
+ This can improve performance for some configurations,
+ but is generally not as effective as using
+ <literal>Decider('MD5-timestamp')</literal>.
</para>
<para>
Now suppose that our "Hello, World!" program
- actually has a <literal>#include</literal> line
+ actually has an <literal>#include</literal> line
to include the &hello_h; file in the compilation:
</para>
the &cv-CPPPATH; variable
may be a list of directories,
or a string separated by
- the system-specific path separate character
+ the system-specific path separation character
(':' on POSIX/Linux, ';' on Windows).
Either way, &SCons; creates the
right command-line options
</section>
<section>
- <title>Ignoring Dependencies: the &Ignore; Method</title>
+ <title>Explicit Dependencies: the &Depends; Function</title>
+
+ <para>
+
+ Sometimes a file depends on another file
+ that is not detected by an &SCons; scanner.
+ For this situation,
+ &SCons; allows you to specific explicitly that one file
+ depends on another file,
+ and must be rebuilt whenever that file changes.
+ This is specified using the &Depends; method:
+
+ </para>
+
+ <programlisting>
+ hello = Program('hello.c')
+ Depends(hello, 'other_file')
+ </programlisting>
+
+ <!-- XXX mention that you can use arrays for target and source? -->
+
+ <screen>
+ % <userinput>scons -Q hello</userinput>
+ cc -c hello.c -o hello.o
+ cc -o hello hello.o
+ % <userinput>scons -Q hello</userinput>
+ scons: `hello' is up to date.
+ % <userinput>edit other_file</userinput>
+ [CHANGE THE CONTENTS OF other_file]
+ % <userinput>scons -Q hello</userinput>
+ cc -c hello.c -o hello.o
+ cc -o hello hello.o
+ </screen>
+
+ </section>
+
+ <section>
+ <title>Ignoring Dependencies: the &Ignore; Function</title>
<para>
Now, the above example is a little contrived,
because it's hard to imagine a real-world situation
- where you wouldn't to rebuild &hello;
+ where you wouldn't want to rebuild &hello;
if the &hello_h; file changed.
A more realistic example
might be if the &hello;
</section>
<section>
- <title>Explicit Dependencies: the &Depends; Method</title>
-
- <para>
-
- On the other hand,
- sometimes a file depends on another file
- that is not detected by an &SCons; scanner.
- For this situation,
- &SCons; allows you to specific explicitly that one file
- depends on another file,
- and must be rebuilt whenever that file changes.
- This is specified using the &Depends; method:
-
- </para>
-
- <programlisting>
- hello = Program('hello.c')
- Depends(hello, 'other_file')
- </programlisting>
-
- <!-- XXX mention that you can use arrays for target and source? -->
-
- <screen>
- % <userinput>scons -Q hello</userinput>
- cc -c hello.c -o hello.o
- cc -o hello hello.o
- % <userinput>scons -Q hello</userinput>
- scons: `hello' is up to date.
- % <userinput>edit other_file</userinput>
- [CHANGE THE CONTENTS OF other_file]
- % <userinput>scons -Q hello</userinput>
- cc -c hello.c -o hello.o
- cc -o hello hello.o
- </screen>
-
- </section>
-
- <section>
- <title>The &AlwaysBuild; Method</title>
+ <title>The &AlwaysBuild; Function</title>
<para>
enabled on the command line,
or different executable programs need to be linked
with different libraries.
- &SCons; accomodates these different build
+ &SCons; accommodates these different build
requirements by allowing you to create and
configure multiple &consenvs;
that control how the software is built.
<para>
- By default, &SCons; intializes every
+ By default, &SCons; initializes every
new construction environment
with a set of &consvars;
based on the tools that it finds on your system,
(Note that because we're not expanding this
in the context of building something
there are no target or source files
- for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
+ for &cv-link-TARGET; and &cv-link-SOURCES; to expand.)
</para>
enabled on the command line,
or different executable programs need to be linked
with different libraries.
- &SCons; accomodates these different build
+ &SCons; accommodates these different build
requirements by allowing you to create and
configure multiple &consenvs;
that control how the software is built.
<para>
- By default, &SCons; intializes every
+ By default, &SCons; initializes every
new construction environment
with a set of &consvars;
based on the tools that it finds on your system,
(Note that because we're not expanding this
in the context of building something
there are no target or source files
- for &cv-link-TARGET; and &cv-link-SOURCES; to expand.
+ for &cv-link-TARGET; and &cv-link-SOURCES; to expand.)
</para>
The &Move; factory
allows you to rename a file or directory.
For example, if we don't want to copy the temporary file,
- we could:
+ we could use:
</para>
a file in a temporary directory
in which the processing tool
will create other files that we don't care about,
- you could:
+ you could use:
</para>
The &Move; factory
allows you to rename a file or directory.
For example, if we don't want to copy the temporary file,
- we could:
+ we could use:
</para>
a file in a temporary directory
in which the processing tool
will create other files that we don't care about,
- you could:
+ you could use:
</para>
By default, &SCons; removes all built targets when invoked
with the <literal>-c</literal> option to clean a source tree
- of built tragets.
+ of built targets.
Sometimes, however, this is not what you want.
For example, you may want to remove only intermediate generated files
(such as object files),
By default, &SCons; removes all built targets when invoked
with the <literal>-c</literal> option to clean a source tree
- of built tragets.
+ of built targets.
Sometimes, however, this is not what you want.
For example, you may want to remove only intermediate generated files
(such as object files),
<para>
- Will display the completely help text on Windows:
+ Will display the complete help text on Windows:
</para>
<para>
- Will display the completely help text on Windows:
+ Will display the complete help text on Windows:
</para>
so that it may be used by other &SConscript; files.
First, you can call the &Export;
function with a list of variables,
- or a string white-space separated variable names.
+ or a string of white-space separated variable names.
Each call to &Export; adds one
or more variables to a global list
of variables that are available for import
<scons_output_command>scons -Q</scons_output_command>
</scons_output>
+ <!--
+ XXX Return(stop=False)
+ -->
+
</section>
</section>
so that it may be used by other &SConscript; files.
First, you can call the &Export;
function with a list of variables,
- or a string white-space separated variable names.
+ or a string of white-space separated variable names.
Each call to &Export; adds one
or more variables to a global list
of variables that are available for import
ranlib libprog.a
</screen>
+ <!--
+ XXX Return(stop=False)
+ -->
+
</section>
</section>
you can either call the &InstallAs; function
multiple times, or as a shorthand,
you can supply same-length lists
- for the both the target and source arguments:
+ for both the target and source arguments:
</para>
you can either call the &InstallAs; function
multiple times, or as a shorthand,
you can supply same-length lists
- for the both the target and source arguments:
+ for both the target and source arguments:
</para>
the library will be found in the
&cv-link-LIBPATH; construction variable:
+ <!-- In the preceding paragraph, the "$" notation for
+ LIBS, LIBPATH etc. is used for the first time.
+ Maybe some words of explanation would be nice. -->
+
</para>
<scons_example name="ex2">
<scons_output example="ex3" os="win32">
<scons_output_command>scons -Q</scons_output_command>
</scons_output>
+ <!-- The link command is too wide in the PDF version.
+ There are some other examples of this throughout the document. -->
<para>
the library will be found in the
&cv-link-LIBPATH; construction variable:
+ <!-- In the preceding paragraph, the "$" notation for
+ LIBS, LIBPATH etc. is used for the first time.
+ Maybe some words of explanation would be nice. -->
+
</para>
<programlisting>
cl /nologo /c prog.c /Foprog.obj
link /nologo /OUT:prog.exe /LIBPATH:\usr\lib /LIBPATH:\usr\local\lib m.lib prog.obj
</screen>
+ <!-- The link command is too wide in the PDF version.
+ There are some other examples of this throughout the document. -->
<para>
XXX GetBuildPath()
XXX GetLaunchDir()
+ XXX ParseConfig()
XXX MergeFlags()
XXX ParseFlags()
XXX GetOption('num_jobs')
XXX SetOption('num_jobs')
+ XXX Options.UnknownOption()
+
+ XXX GetBuildFailures()
+
+ XXX Requires()
+
+ XXX CheckTypeSize()
+
+ XXX Glob()
+
+ XXX Progress()
+
+ XXX AddMethod()
+
XXX - - diskcheck=
XXX site_scons
XXX GetBuildPath()
XXX GetLaunchDir()
+ XXX ParseConfig()
XXX MergeFlags()
XXX ParseFlags()
XXX GetOption('num_jobs')
XXX SetOption('num_jobs')
+ XXX Options.UnknownOption()
+
+ XXX GetBuildFailures()
+
+ XXX Requires()
+
+ XXX CheckTypeSize()
+
+ XXX Glob()
+
+ XXX Progress()
+
+ XXX AddMethod()
+
XXX - - diskcheck=
XXX site_scons
<scons_example name="ParseConfig1">
<file name="SConstruct" printme="1">
env = Environment()
- env.ParseConfig("pkg-config")
+ env.ParseConfig("pkg-config x11")
</file>
<file name="f1.c">
int f1() { }
<programlisting>
env = Environment()
- env.ParseConfig("pkg-config")
+ env.ParseConfig("pkg-config x11")
</programlisting>
<para>
<screen>
% <userinput>scons -Q</userinput>
scons: `.' is up to date.
- Must specify package names on the command line
</screen>
<para>
Usually, this would be done by a build integrator
who would run &SCons; in the repository
to create all of its derived files and &sconsign; files,
- or who would &SCons; in a separate build directory
- and copying the resulting tree to the desired repository:
+ or who would run &SCons; in a separate build directory
+ and copy the resulting tree to the desired repository:
</para>
<para>
Notice that &SCons; realizes that it does not need to
- rebuild local copies file1.o and file2.o files,
+ rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files,
but instead uses the already-compiled files
from the repository.
Usually, this would be done by a build integrator
who would run &SCons; in the repository
to create all of its derived files and &sconsign; files,
- or who would &SCons; in a separate build directory
- and copying the resulting tree to the desired repository:
+ or who would run &SCons; in a separate build directory
+ and copy the resulting tree to the desired repository:
</para>
<para>
Notice that &SCons; realizes that it does not need to
- rebuild local copies file1.o and file2.o files,
+ rebuild local copies <filename>file1.o</filename> and <filename>file2.o</filename> files,
but instead uses the already-compiled files
from the repository.
Note that you can choose to terminate
the build if a given header file doesn't exist,
- or you can modify the contstruction environment
+ or you can modify the construction environment
based on the existence of a header file.
</para>
Note that you can choose to terminate
the build if a given header file doesn't exist,
- or you can modify the contstruction environment
+ or you can modify the construction environment
based on the existence of a header file.
</para>
Because we want this User's Guide to focus
on what &SCons; is actually doing,
- we're going use the &Q; option
+ we're going to use the &Q; option
to remove these messages from the
output of all the remaining examples in this Guide.
Because we want this User's Guide to focus
on what &SCons; is actually doing,
- we're going use the &Q; option
+ we're going to use the &Q; option
to remove these messages from the
output of all the remaining examples in this Guide.
<screen>
% <userinput>scons -Q</userinput>
- bk get -
bk get hello.c
cc -o hello.o -c hello.c
cc -o hello hello.o
<screen>
% <userinput>scons -Q</userinput>
- cvs -d /usr/local/CVS co -
cvs -d /usr/local/CVS co hello.c
cc -o hello.o -c hello.c
cc -o hello hello.o
<screen>
% <userinput>scons -Q</userinput>
- co -
co hello.c
cc -o hello.o -c hello.c
cc -o hello hello.o
<screen>
% <userinput>scons -Q</userinput>
- sccs get -
sccs get hello.c
cc -o hello.o -c hello.c
cc -o hello hello.o
Although this is usually helpful and what you want,
it might be frustrating if &SCons;
doesn't set certain variables that you
- expect to be sit.
+ expect to be set.
In situations like this,
it's sometimes helpful to use the
construction environment &Dump; method
In a real-life situation,
the construction environments will
likely contain a great many more variables.
+ Also note that we've massaged the example output above
+ to make the memory address of all objects a constant 0x700000.
+ In reality, you would see a different hexadecimal
+ number for each object.
</para>
Although this is usually helpful and what you want,
it might be frustrating if &SCons;
doesn't set certain variables that you
- expect to be sit.
+ expect to be set.
In situations like this,
it's sometimes helpful to use the
construction environment &Dump; method
<screen>
% <userinput>scons</userinput>
scons: Reading SConscript files ...
- { 'BUILDERS': {'InstallAs': <function InstallAsBuilderWrapper at 0xb23a28>, 'Install': <function InstallBuilderWrapper at 0xb1b7d0>},
+ { 'BUILDERS': {'InstallAs': <function InstallAsBuilderWrapper at 0x700000>, 'Install': <function InstallBuilderWrapper at 0x700000>},
'CONFIGUREDIR': '#/.sconf_temp',
'CONFIGURELOG': '#/config.log',
'CPPSUFFIXES': [ '.c',
'.spp',
'.SPP'],
'DSUFFIXES': ['.d'],
- 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48>,
- 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90>,
+ 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
+ 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'ENV': {'PATH': '/usr/local/bin:/opt/bin:/bin:/usr/bin'},
- 'ESCAPE': <function escape at 0xb1fa28>,
- 'File': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8>,
+ 'ESCAPE': <function escape at 0x700000>,
+ 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'IDLSUFFIXES': ['.idl', '.IDL'],
- 'INSTALL': <function copyFunc at 0xb23aa0>,
+ 'INSTALL': <function copyFunc at 0x700000>,
'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'],
'LIBPREFIX': 'lib',
'LIBPREFIXES': '$LIBPREFIX',
'PLATFORM': 'posix',
'PROGPREFIX': '',
'PROGSUFFIX': '',
- 'PSPAWN': <function piped_env_spawn at 0xb23230>,
- 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20>,
+ 'PSPAWN': <function piped_env_spawn at 0x700000>,
+ 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'SCANNERS': [],
'SHELL': 'sh',
'SHLIBPREFIX': '$LIBPREFIX',
'SHLIBSUFFIX': '.so',
'SHOBJPREFIX': '$OBJPREFIX',
'SHOBJSUFFIX': '$OBJSUFFIX',
- 'SPAWN': <function spawnvpe_spawn at 0xb1f7d0>,
- 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0xa4e170>,
+ 'SPAWN': <function spawnvpe_spawn at 0x700000>,
+ 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>,
'TEMPFILEPREFIX': '@',
'TOOLS': ['install', 'install'],
'_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
'_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
'__RPATH': '$_RPATH',
- '_concat': <function _concat at 0xa3d398>,
- '_defines': <function _defines at 0xa3d500>,
- '_stripixes': <function _stripixes at 0xa3d488>}
+ '_concat': <function _concat at 0x700000>,
+ '_defines': <function _defines at 0x700000>,
+ '_stripixes': <function _stripixes at 0x700000>}
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
<screen>
C:\><userinput>scons</userinput>
scons: Reading SConscript files ...
- { 'BUILDERS': {'RES': <SCons.Builder.BuilderBase instance at 0xb39518>, 'Object': <SCons.Builder.CompositeBuilder instance at 0xb4a710>, 'InstallAs': <function InstallAsBuilderWrapper at 0xb45c08>, 'PCH': <SCons.Builder.BuilderBase instance at 0xb1cef0>, 'Install': <function InstallBuilderWrapper at 0xb1b7d0>, 'SharedObject': <SCons.Builder.CompositeBuilder instance at 0xb4aa28>, 'StaticObject': <SCons.Builder.CompositeBuilder instance at 0xb4a710>},
+ { 'BUILDERS': {'RES': <SCons.Builder.BuilderBase instance at 0x700000>, 'Object': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'InstallAs': <function InstallAsBuilderWrapper at 0x700000>, 'PCH': <SCons.Builder.BuilderBase instance at 0x700000>, 'Install': <function InstallBuilderWrapper at 0x700000>, 'SharedObject': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'StaticObject': <SCons.Builder.CompositeBuilder instance at 0x700000>},
'CC': 'cl',
- 'CCCOM': <SCons.Action.FunctionAction instance at 0xb4c290>,
+ 'CCCOM': <SCons.Action.FunctionAction instance at 0x700000>,
'CCCOMFLAGS': '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS',
'CCFLAGS': ['/nologo'],
'CCPCHFLAGS': ['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'],
'CXXFILESUFFIX': '.cc',
'CXXFLAGS': ['$CCFLAGS', '$(', '/TP', '$)'],
'DSUFFIXES': ['.d'],
- 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb48>,
- 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1eb90>,
+ 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
+ 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'ENV': { 'INCLUDE': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\include',
'LIB': 'C:\\Program Files\\Microsoft Visual Studio/VC98\\lib',
'PATH': 'C:\\Program Files\\Microsoft Visual Studio\\Common\\tools\\WIN95;C:\\Program Files\\Microsoft Visual Studio\\Common\\MSDev98\\bin;C:\\Program Files\\Microsoft Visual Studio\\Common\\tools;C:\\Program Files\\Microsoft Visual Studio/VC98\\bin',
'PATHEXT': '.COM;.EXE;.BAT;.CMD',
'SystemRoot': 'C:/WINDOWS'},
- 'ESCAPE': <function escape at 0xb24848>,
- 'File': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ebd8>,
+ 'ESCAPE': <function escape at 0x700000>,
+ 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'IDLSUFFIXES': ['.idl', '.IDL'],
'INCPREFIX': '/I',
'INCSUFFIX': '',
- 'INSTALL': <function copyFunc at 0xb45c80>,
+ 'INSTALL': <function copyFunc at 0x700000>,
'LATEXSUFFIXES': ['.tex', '.ltx', '.latex'],
'LIBPREFIX': '',
'LIBPREFIXES': ['$LIBPREFIX'],
'PLATFORM': 'win32',
'PROGPREFIX': '',
'PROGSUFFIX': '.exe',
- 'PSPAWN': <function piped_spawn at 0xb20488>,
+ 'PSPAWN': <function piped_spawn at 0x700000>,
'RC': 'rc',
'RCCOM': '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES',
'RCFLAGS': [],
- 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0xa1ec20>,
+ 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>,
'SCANNERS': [],
'SHCC': '$CC',
- 'SHCCCOM': <SCons.Action.FunctionAction instance at 0xb4c320>,
+ 'SHCCCOM': <SCons.Action.FunctionAction instance at 0x700000>,
'SHCCFLAGS': ['$CCFLAGS'],
'SHCFLAGS': ['$CFLAGS'],
'SHCXX': '$CXX',
'SHLIBSUFFIX': '.dll',
'SHOBJPREFIX': '$OBJPREFIX',
'SHOBJSUFFIX': '$OBJSUFFIX',
- 'SPAWN': <function spawn at 0xb247d0>,
+ 'SPAWN': <function spawn at 0x700000>,
'STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME': 1,
- 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0xa4e170>,
+ 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>,
'TEMPFILEPREFIX': '@',
'TOOLS': ['msvc', 'install', 'install'],
'_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
'_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
- '_concat': <function _concat at 0xa3d398>,
- '_defines': <function _defines at 0xa3d500>,
- '_stripixes': <function _stripixes at 0xa3d488>}
+ '_concat': <function _concat at 0x700000>,
+ '_defines': <function _defines at 0x700000>,
+ '_stripixes': <function _stripixes at 0x700000>}
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
In a real-life situation,
the construction environments will
likely contain a great many more variables.
+ Also note that we've massaged the example output above
+ to make the memory address of all objects a constant 0x700000.
+ In reality, you would see a different hexadecimal
+ number for each object.
</para>
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
+-.
- +--
+-SConstruct
+-f1.c
+-f1.o
H = no cache
[E b ]+-.
- [ ] +--
- [E ] +-SConstruct
- [E ] +-f1.c
+ [E C ] +-SConstruct
+ [E C ] +-f1.c
[E B C ] +-f1.o
- [E ] | +-f1.c
- [E ] | +-inc.h
- [E ] +-f2.c
+ [E C ] | +-f1.c
+ [E C ] | +-inc.h
+ [E C ] +-f2.c
[E B C ] +-f2.o
- [E ] | +-f2.c
- [E ] | +-inc.h
- [E ] +-f3.c
+ [E C ] | +-f2.c
+ [E C ] | +-inc.h
+ [E C ] +-f3.c
[E B C ] +-f3.o
- [E ] | +-f3.c
- [E ] | +-inc.h
- [E ] +-inc.h
+ [E C ] | +-f3.c
+ [E C ] | +-inc.h
+ [E C ] +-inc.h
[E B C ] +-prog
[E B C ] +-f1.o
- [E ] | +-f1.c
- [E ] | +-inc.h
+ [E C ] | +-f1.c
+ [E C ] | +-inc.h
[E B C ] +-f2.o
- [E ] | +-f2.c
- [E ] | +-inc.h
+ [E C ] | +-f2.c
+ [E C ] | +-inc.h
[E B C ] +-f3.o
- [E ] +-f3.c
- [E ] +-inc.h
+ [E C ] +-f3.c
+ [E C ] +-inc.h
</screen>
<para>
cc -o f3.o -c -I. f3.c
cc -o prog f1.o f2.o f3.o
+-.
+ +-f1.o
+ +-f2.o
+ +-f3.o
+ +-prog
+ +-f1.o
+ +-f2.o
+ +-f3.o
</screen>
<para>
cc -o prog2.o -c -I. prog2.c
cc -o prog2 prog2.o -L. -lfoo
+-.
- +--
+-SConstruct
+-f1.c
+-f1.o
cc -o prog2.o -c -I. prog2.c
cc -o prog2 prog2.o -L. -lfoo
+-.
- +--
+-SConstruct
+-f1.c
+-f1.o
scons: *** Source `prog.c' not found, needed by target `prog.o'. Stop.
scons: internal stack trace:
File "bootstrap/src/engine/SCons/Job.py", line 114, in start
+ File "bootstrap/src/engine/SCons/Script/Main.py", line 157, in prepare
File "bootstrap/src/engine/SCons/Taskmaster.py", line 169, in prepare
- File "bootstrap/src/engine/SCons/Node/FS.py", line 2220, in prepare
- File "bootstrap/src/engine/SCons/Node/__init__.py", line 819, in prepare
+ File "bootstrap/src/engine/SCons/Node/FS.py", line 2568, in prepare
+ File "bootstrap/src/engine/SCons/Node/__init__.py", line 349, in prepare
</screen>
<para>
% <userinput>scons -Q --taskmastertrace=- prog</userinput>
Taskmaster: 'prog': children:
['prog.o']
- waiting on unstarted children:
+ waiting on unfinished children:
['prog.o']
+ Taskmaster: 'prog.o': children:
+ ['inc.h', 'prog.c']
+ waiting on unfinished children:
+ ['inc.h', 'prog.c']
+ Taskmaster: 'prog.c': evaluating prog.c
+ Taskmaster: 'inc.h': evaluating inc.h
Taskmaster: 'prog.o': children:
['inc.h', 'prog.c']
evaluating prog.o
['prog.o']
evaluating prog
cc -o prog prog.o
- Taskmaster: 'prog': already handled (executed)
</screen>
<para>
-RELEASE 0.XX - XXX
+RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
+
+ From Benoit Belley:
+
+ - Fix occasional spurious rebuilds and inefficiency when using
+ --implicit-cache and Builders that produce multiple targets.
+
+ - Allow SCons to not have to know about the builders of generated
+ files when BuildDir(duplicate=0) is used, potentially allowing some
+ SConscript files to be ignored for smaller builds.
+
+ From David Cournapeau:
+
+ - Add a CheckTypeSize() call to configure contexts.
+
+ From Ken Deeter:
+
+ - Make the "contents" of Alias Nodes a concatenation of the children's
+ content signatures (MD5 checksums), not a concatenation of the
+ children's contents, to avoid using large amounts of memory during
+ signature calculation.
+
+ From Malte Helmert:
+
+ - Fix a lot of typos in the man page and User's Guide.
+
+ From Geoffrey Irving:
+
+ - Speed up conversion of paths in .sconsign files to File or Dir Nodes.
+
+ From Steven Knight:
+
+ - Add an Options.UnknownOptions() method that returns any settings
+ (from the command line, or whatever dictionary was passed in)
+ that aren't known to the Options object.
+
+ - Add a Glob() function.
+
+ - When removing targets with the -c option, use the absolute path (to
+ avoid problems interpreting BuildDir() when the top-level directory
+ is the source directory).
+
+ - Fix problems with Install() and InstallAs() when called through a
+ clone (of a clone, ...) of a cloned construction environment.
+
+ - When executing a file containing Options() settings, add the file's
+ directory to sys.path (so modules can be imported from there) and
+ explicity set __name__ to the name of the file so the statement's
+ in the file can deduce the location if they need to.
+
+ - Fix an O(n^2) performance problem when adding sources to a target
+ through calls to a multi Builder (including Aliases).
+
+ - Redefine the $WINDOWSPROGMANIFESTSUFFIX and
+ $WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to
+ the underlying $SHLIBSUFFIX and $PROGSUFFIX variables.
+
+ - Add a GetBuildFailures() function that can be called from functions
+ registered with the Python atexit module to print summary information
+ about any failures encountered while building.
+
+ - Return a NodeList object, not a Python list, when a single_source
+ Builder like Object() is called with more than one file.
+
+ - When searching for implicit dependency files in the directories
+ in a $*PATH list, don't create Dir Nodes for directories that
+ don't actually exist on-disk.
+
+ - Add a Requires() function to allow the specification of order-only
+ prerequisites, which will be updated before specified "downstream"
+ targets but which don't actually cause the target to be rebuilt.
+
+ - Restore the FS.{Dir,File,Entry}.rel_path() method.
+
+ - Make the default behavior of {Source,Target}Signatures('timestamp')
+ be equivalent to 'timestamp-match', not 'timestamp-newer'.
+
+ - Fix use of CacheDir with Decider('timestamp-newer') by updating
+ the modification time when copying files from the cache.
+
+ - Fix random issues with parallel (-j) builds on Windows when Python
+ holds open file handles (especially for SCons temporary files,
+ or targets built by Python function actions) across process creation.
+
+ From Maxim Kartashev:
+
+ - Fix test scripts when run on Solaris.
+
+ From Gary Oberbrunner:
+
+ - Fix Glob() when a pattern is in an explicitly-named subdirectory.
+
+ From Philipp Scholl:
+
+ - Fix setting up targets if multiple Package builders are specified
+ at once.
+
+
+
+RELEASE 0.97.0d20070918 - Tue, 18 Sep 2007 10:51:27 -0500
From Steven Knight:
-RELEASE 0.97.0d200709XX - XXX
+RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600
This is the eighth beta release of SCons. Please consult the
CHANGES.txt file for a list of specific changes since last release.
+ Please note the following important changes since release 0.97.0d20070918:
+
+ -- SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS
+ ON OPEN FILE HANDLES TO CREATED PROCESSES
+
+ On Windows systems, SCons now redefines the Python open()
+ and file() functions so that, if the Python Win32 extensions
+ are available, the file handles for any opened files will *not*
+ be inherited by subprocesses, such as the spawned compilers and
+ other tools invoked to build the software.
+
+ This prevents certain race conditions where a file handle for
+ a file opened by Python (either in a Python function action,
+ or directly in a SConscript file) could be inherited and help
+ open by a subprocess, interfering with the ability of other
+ processes to create or modify the file.
+
+ In general, this should not cause problems for the vast majority
+ of configurations. The only time this would be a problem would be
+ in the unlikely event that a process spawned by SCons specifically
+ *expected* to use an inherited file handle opened by SCons.
+
+ If the Python Win32 extensions are not installed or are an
+ earlier version that does not have the ability to disable file
+ handle inheritance, SCons will print a warning message when the
+ -j option is used. The warning message may be suppressed by
+ specifying --warn=no-parallel-support.
+
Please note the following important changes since release 0.97.0d20070809:
-- "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR
You can guarantee that a list will be updated in place regardless
of which SConscript file defines it and which adds to it by
- using the list append() method as follows:
+ using the list extend() method as follows:
- obj.append(env.Object('foo.c'))
+ obj.extend(env.Object('foo.c'))
Please note the following important changes since release 0.96.1:
os.chdir(chdir)
try:
stat = self.execute(target, source, env)
- stat = exitstatfunc(stat)
+ if isinstance(stat, SCons.Errors.BuildError):
+ s = exitstatfunc(stat.status)
+ if s:
+ stat.status = s
+ else:
+ stat = s
+ else:
+ stat = exitstatfunc(stat)
finally:
if save_cwd:
os.chdir(save_cwd)
cmd_line = escape_list(cmd_line, escape)
result = spawn(shell, escape, cmd_line[0], cmd_line, ENV)
if not ignore and result:
- return result
+ msg = "Error %s" % result
+ return SCons.Errors.BuildError(errstr=msg,
+ status=result,
+ action=self,
+ command=cmd_line)
return 0
def get_contents(self, target, source, env):
# target file will appear).
try: filename = e.filename
except AttributeError: filename = None
- raise SCons.Errors.BuildError(node=target,
- errstr=e.strerror,
- filename=filename)
+ result = SCons.Errors.BuildError(node=target,
+ errstr=e.strerror,
+ status=1,
+ filename=filename,
+ action=self,
+ command=self.strfunction(target, source, env))
+ else:
+ if result:
+ msg = "Error %s" % result
+ result = SCons.Errors.BuildError(errstr=msg,
+ status=result,
+ action=self,
+ command=self.strfunction(target, source, env))
return result
def get_contents(self, target, source, env):
# was called by using this hard-coded value as a special return.
if s == '$__env__':
return env
- else:
+ elif SCons.Util.is_String(s):
return env.subst(s, 0, target, source)
+ return self.parent.convert(s)
def subst_args(self, target, source, env):
return map(lambda x, self=self, t=target, s=source, e=env:
self.subst(x, t, s, e),
called with and give them to the ActionCaller object we create,
so it can hang onto them until it needs them.
"""
- def __init__(self, actfunc, strfunc):
+ def __init__(self, actfunc, strfunc, convert=lambda x: x):
self.actfunc = actfunc
self.strfunc = strfunc
+ self.convert = convert
def __call__(self, *args, **kw):
ac = ActionCaller(self, args, kw)
action = Action(ac, strfunction=ac.strfunction)
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env)
- assert result == 7, result
+ assert result.status == 7, result
s = sio.getvalue()
assert s == 'execfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == expect % (repr('xyz'), repr(test.workpath())), s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env, chdir='sub')
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == expect % (repr('sub'), repr(test.workpath())), s
sio = StringIO.StringIO()
sys.stdout = sio
result = b("out", "in", env)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'firstfunc(["out"], ["in"])\nexecfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env, presub=0)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'execfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env, presub=1)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = b(["out"], "in", env, presub=1)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'Building out with action:\n firstfunc(target, source, env)\nfirstfunc(["out"], ["in"])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = b(["out", "list"], "in", env, presub=1)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(["out", "list"], ["in"])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(["out", "list"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a2("out", "in", env)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'Building out with action:\n execfunc(target, source, env)\nexecfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a2("out", "in", env, presub=0)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == 'execfunc(["out"], ["in"])\n', s
sio = StringIO.StringIO()
sys.stdout = sio
result = a("out", "in", env, presub=0, execute=1, show=0)
- assert result == 7, result
+ assert result.status == 7, result.status
s = sio.getvalue()
assert s == '', s
assert exitstatfunc_result == [], exitstatfunc_result
result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc)
- assert result == 7, result
+ assert result.status == 7, result.status
assert exitstatfunc_result == [7], exitstatfunc_result
SCons.Action.execute_actions = 1
m = 'Invalid command display variable'
assert string.find(s, m) != -1, 'Unexpected string: %s' % s
else:
- raise "did not catch expected UserError"
+ raise Exception, "did not catch expected UserError"
def test___str__(self):
"""Test fetching the pre-substitution string for command Actions
# Test that a nonexistent command returns 127
act = SCons.Action.CommandAction(python + "_no_such_command_")
r = act([], [], env.Clone(out = outfile))
- assert r == expect_nonexistent, "r == %d" % r
+ assert r.status == expect_nonexistent, r.status
# Test that trying to execute a directory returns 126
dir, tail = os.path.split(python)
act = SCons.Action.CommandAction(dir)
r = act([], [], env.Clone(out = outfile))
- assert r == expect_nonexecutable, "r == %d" % r
+ assert r.status == expect_nonexecutable, r.status
# Test that trying to execute a non-executable file returns 126
act = SCons.Action.CommandAction(outfile)
r = act([], [], env.Clone(out = outfile))
- assert r == expect_nonexecutable, "r == %d" % r
+ assert r.status == expect_nonexecutable, r.status
act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py))
r = act([], [], env)
- assert r == 1, r
+ assert r.status == 1, r.status
act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py))
r = act([], [], env)
- assert r == 1, r
+ assert r.status == 1, r.status
act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py))
r = act([], [], env)
act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py))
r = act([], [], env)
- assert r == 1, r
+ assert r.status == 1, r.status
act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py))
r = act([], [], env)
return 1
act = SCons.Action.FunctionAction(function1)
- r = None
- try:
- r = act(target = [outfile, outfile2], source=[], env=Environment())
- except SCons.Errors.BuildError:
- pass
- assert r == 1
- assert count == 1
+ r = act(target = [outfile, outfile2], source=[], env=Environment())
+ assert r.status == 1, r.status
+
+ assert count == 1, count
c = test.read(outfile, 'r')
assert c == "function1\n", c
c = test.read(outfile2, 'r')
act = SCons.Action.FunctionAction(class1a)
r = act([], [], Environment(out = outfile))
- assert r.__class__ == class1a
+ assert isinstance(r.status, class1a), r.status
c = test.read(outfile, 'r')
assert c == "class1a\n", c
act = SCons.Action.FunctionAction(class1b())
r = act([], [], Environment(out = outfile))
- assert r == 2
+ assert r.status == 2, r.status
c = test.read(outfile, 'r')
assert c == "class1b\n", c
open(env['out'], 'a').write("class2b\n")
act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b])
r = act([], [], Environment(out = outfile))
- assert r.__class__ == class2b
+ assert isinstance(r.status, class2b), r.status
c = test.read(outfile, 'r')
assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c
if not tgt is None: tgt = [tgt]
if not src is None: src = [src]
result.extend(self._execute(env, tgt, src, overwarn))
- return result
+ return SCons.Node.NodeList(result)
overwarn.warn()
single_source = 1, suffix='.out')
env['CNT'] = [0]
tgt = builder(env, target=outfiles[0], source=infiles[0])[0]
+ s = str(tgt)
+ assert s == test.workpath('0.out'), s
tgt.prepare()
tgt.build()
assert env['CNT'][0] == 1, env['CNT'][0]
tgt = builder(env, outfiles[1], infiles[1])[0]
+ s = str(tgt)
+ assert s == test.workpath('1.out'), s
tgt.prepare()
tgt.build()
assert env['CNT'][0] == 2
tgts = builder(env, None, infiles[2:4])
+ try:
+ [].extend(UserList.UserList())
+ except TypeError:
+ # Old Python version (1.5.2) that can't handle extending
+ # a list with list-like objects. That means the return
+ # value from the builder call is a real list with Nodes,
+ # and doesn't have a __str__() method that stringifies
+ # the individual elements. Since we're gong to drop 1.5.2
+ # support anyway, don't bother trying to test for it.
+ pass
+ else:
+ s = str(tgts)
+ expect = str([test.workpath('2.out'), test.workpath('3.out')])
+ assert s == expect, s
for t in tgts: t.prepare()
tgts[0].build()
tgts[1].build()
if fs.islink(cachefile):
fs.symlink(fs.readlink(cachefile), t.path)
else:
- fs.copy2(cachefile, t.path)
+ env.copy_from_cache(cachefile, t.path)
st = fs.stat(cachefile)
fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
return 0
return ret
+def CheckTypeSize(context, type_name, header = None, language = None, expect = None):
+ """This check can be used to get the size of a given type, or to check whether
+ the type is of expected size.
+
+ Arguments:
+ - type : str
+ the type to check
+ - includes : sequence
+ list of headers to include in the test code before testing the type
+ - language : str
+ 'C' or 'C++'
+ - expect : int
+ if given, will test wether the type has the given number of bytes.
+ If not given, will automatically find the size.
+
+ Returns:
+ status : int
+ 0 if the check failed, or the found size of the type if the check succeeded."""
+
+ # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
+ if context.headerfilename:
+ includetext = '#include "%s"' % context.headerfilename
+ else:
+ includetext = ''
+
+ if not header:
+ header = ""
+
+ lang, suffix, msg = _lang2suffix(language)
+ if msg:
+ context.Display("Cannot check for %s type: %s\n" % (type_name, msg))
+ return msg
+
+ src = includetext + header
+ if not expect is None:
+ # Only check if the given size is the right one
+ context.Display('Checking %s is %d bytes... ' % (type_name, expect))
+
+ # test code taken from autoconf: this is a pretty clever hack to find that
+ # a type is of a given size using only compilation. This speeds things up
+ # quite a bit compared to straightforward code using TryRun
+ src = src + r"""
+typedef %s scons_check_type;
+
+int main()
+{
+ static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)];
+ test_array[0] = 0;
+
+ return 0;
+}
+"""
+
+ # XXX: Try* vs CompileProg ?
+ st = context.TryCompile(src % (type_name, expect), suffix)
+ if st:
+ _Have(context, "SIZEOF_" + type_name, str(expect))
+ context.Display("yes\n")
+ return expect
+ else:
+ context.Display("no\n")
+ _LogFailed(context, src, st)
+ return 0
+ else:
+ # Only check if the given size is the right one
+ context.Message('Checking size of %s ... ' % type_name)
+
+ # We have to be careful with the program we wish to test here since
+ # compilation will be attempted using the current environment's flags.
+ # So make sure that the program will compile without any warning. For
+ # example using: 'int main(int argc, char** argv)' will fail with the
+ # '-Wall -Werror' flags since the variables argc and argv would not be
+ # used in the program...
+ #
+ src = src + """
+#include <stdlib.h>
+#include <stdio.h>
+int main() {
+ printf("%d", (int)sizeof(""" + type_name + """));
+ return 0;
+}
+ """
+ ret = context.TryRun(src, suffix)
+ st = ret[0]
+ try:
+ size = int(ret[1])
+ _Have(context, "SIZEOF_" + type_name, str(size))
+ context.Display("%d\n" % size)
+ except ValueError:
+ size = 0
+ _LogFailed(context, src, st)
+ context.Display(" Failed !\n")
+ if st:
+ return size
+ else:
+ return 0
def CheckLib(context, libs, func_name = None, header = None,
extra_libs = None, call = None, language = None, autoadd = 1):
if not _default_env:
import SCons.Util
_default_env = apply(SCons.Environment.Environment, args, kw)
- _default_env.TargetSignatures('source')
if SCons.Util.md5:
- _default_env.SourceSignatures('MD5')
+ _default_env.Decider('MD5')
else:
- _default_env.SourceSignatures('timestamp')
+ _default_env.Decider('timestamp-match')
global DefaultEnvironment
DefaultEnvironment = _fetch_DefaultEnvironment
_default_env._CacheDir = SCons.CacheDir.Null()
# ways by creating ActionFactory instances.
ActionFactory = SCons.Action.ActionFactory
-Chmod = ActionFactory(os.chmod,
+def chmod_func(path, mode):
+ return os.chmod(str(path), mode)
+
+Chmod = ActionFactory(chmod_func,
lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode))
def copy_func(dest, src):
return shutil.copytree(src, dest, 1)
Copy = ActionFactory(copy_func,
- lambda dest, src: 'Copy("%s", "%s")' % (dest, src))
+ lambda dest, src: 'Copy("%s", "%s")' % (dest, src),
+ convert=str)
def delete_func(entry, must_exist=0):
+ entry = str(entry)
if not must_exist and not os.path.exists(entry):
return None
if not os.path.exists(entry) or os.path.isfile(entry):
Delete = ActionFactory(delete_func, delete_strfunc)
Mkdir = ActionFactory(os.makedirs,
- lambda dir: 'Mkdir("%s")' % dir)
+ lambda dir: 'Mkdir("%s")' % dir,
+ convert=str)
Move = ActionFactory(lambda dest, src: os.rename(src, dest),
- lambda dest, src: 'Move("%s", "%s")' % (dest, src))
+ lambda dest, src: 'Move("%s", "%s")' % (dest, src),
+ convert=str)
def touch_func(file):
+ file = str(file)
mtime = int(time.time())
if os.path.exists(file):
atime = os.path.getatime(file)
def __repr__(self):
return '<BuilderWrapper %s>' % repr(self.name)
+ def __str__(self):
+ return self.__repr__()
+
def __getattr__(self, name):
if name == 'env':
return self.object
return self.__class__(self.data, self.env)
def __setitem__(self, item, val):
+ try:
+ method = getattr(self.env, item).method
+ except AttributeError:
+ pass
+ else:
+ self.env.RemoveMethod(method)
UserDict.__setitem__(self, item, val)
BuilderWrapper(self.env, val, item)
f = SCons.Defaults.DefaultEnvironment().decide_target
return f(dependency, target, prev_ni)
+def default_copy_from_cache(src, dst):
+ f = SCons.Defaults.DefaultEnvironment().copy_from_cache
+ return f(src, dst)
+
class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction
self.decide_target = default_decide_target
self.decide_source = default_decide_source
+ self.copy_from_cache = default_copy_from_cache
+
self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
if platform is None:
clone.added_methods = []
for mw in self.added_methods:
- mw.clone(clone)
+ clone.added_methods.append(mw.clone(clone))
clone._memo = {}
def _changed_timestamp_match(self, dependency, target, prev_ni):
return dependency.changed_timestamp_match(target, prev_ni)
+ def _copy_from_cache(self, src, dst):
+ return self.fs.copy(src, dst)
+
+ def _copy2_from_cache(self, src, dst):
+ return self.fs.copy2(src, dst)
+
def Decider(self, function):
+ copy_function = self._copy2_from_cache
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_timestamp_then_content
elif function in ('timestamp-newer', 'make'):
function = self._changed_timestamp_newer
+ copy_function = self._copy_from_cache
elif function == 'timestamp-match':
function = self._changed_timestamp_match
elif not callable(function):
self.decide_target = function
self.decide_source = function
+ self.copy_from_cache = copy_function
+
def Detect(self, progs):
"""Return the first available program in progs.
"""
"""Directly execute an action through an Environment
"""
action = apply(self.Action, (action,) + args, kw)
- return action([], [], self)
+ result = action([], [], self)
+ if isinstance(result, SCons.Errors.BuildError):
+ return result.status
+ else:
+ return result
def File(self, name, *args, **kw):
"""
else:
return result[0]
+ def Glob(self, pattern, ondisk=True, source=False, strings=False):
+ return self.fs.Glob(self.subst(pattern), ondisk, source, strings)
+
def Ignore(self, target, dependency):
"""Ignore a dependency."""
tlist = self.arg2nodes(target, self.fs.Entry)
dirs = self.arg2nodes(list(dirs), self.fs.Dir)
apply(self.fs.Repository, dirs, kw)
+ def Requires(self, target, prerequisite):
+ """Specify that 'prerequisite' must be built before 'target',
+ (but 'target' does not actually depend on 'prerequisite'
+ and need not be rebuilt if it changes)."""
+ tlist = self.arg2nodes(target, self.fs.Entry)
+ plist = self.arg2nodes(prerequisite, self.fs.Entry)
+ for t in tlist:
+ t.add_prerequisite(plist)
+ return tlist
+
def Scanner(self, *args, **kw):
nargs = []
for arg in args:
raise UserError, "MD5 signatures are not available in this version of Python."
self.decide_source = self._changed_content
elif type == 'timestamp':
- self.decide_source = self._changed_timestamp_newer
+ self.decide_source = self._changed_timestamp_match
else:
raise UserError, "Unknown source signature type '%s'" % type
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
+ self.decide_target = self._changed_timestamp_match
elif type == 'build':
self.decide_target = self._changed_build
elif type == 'source':
r = env.func('-yyy')
assert r == 'func2-foo-yyy', r
+ # Test that clones of clones correctly re-bind added methods.
+ env1 = Environment(FOO = '1')
+ env1.AddMethod(func2)
+ env2 = env1.Clone(FOO = '2')
+ env3 = env2.Clone(FOO = '3')
+ env4 = env3.Clone(FOO = '4')
+ r = env1.func2()
+ assert r == 'func2-1', r
+ r = env2.func2()
+ assert r == 'func2-2', r
+ r = env3.func2()
+ assert r == 'func2-3', r
+ r = env4.func2()
+ assert r == 'func2-4', r
+
def test_Override(self):
"Test overriding construction variables"
env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4)
class BuildError(Exception):
- def __init__(self, node=None, errstr="Unknown error", filename=None, *args):
+ def __init__(self, node=None, errstr="Unknown error", status=0,
+ filename=None, executor=None, action=None, command=None,
+ *args):
self.node = node
self.errstr = errstr
+ self.status = status
self.filename = filename
+ self.executor = executor
+ self.action = action
+ self.command = command
apply(Exception.__init__, (self,) + args)
class InternalError(Exception):
import string
from SCons.Debug import logInstanceCreation
+import SCons.Errors
import SCons.Memoize
self.overridelist = overridelist
self.targets = targets
self.sources = sources[:]
+ self.sources_need_sorting = False
self.builder_kw = builder_kw
self._memo = {}
cwd = self.targets[0].cwd
except (IndexError, AttributeError):
cwd = None
- return scanner.path(env, cwd, self.targets, self.sources)
+ return scanner.path(env, cwd, self.targets, self.get_sources())
def get_kw(self, kw={}):
result = self.builder_kw.copy()
kw = self.get_kw(kw)
status = 0
for act in self.get_action_list():
- status = apply(act, (self.targets, self.sources, env), kw)
- if status:
- break
+ status = apply(act, (self.targets, self.get_sources(), env), kw)
+ if isinstance(status, SCons.Errors.BuildError):
+ status.executor = self
+ raise status
+ elif status:
+ msg = "Error %s" % status
+ raise SCons.Errors.BuildError(errstr=msg, executor=self, action=act)
return status
# use extra indirection because with new-style objects (Python 2.2
"""Add source files to this Executor's list. This is necessary
for "multi" Builders that can be called repeatedly to build up
a source file list for a given target."""
- slist = filter(lambda x, s=self.sources: x not in s, sources)
- self.sources.extend(slist)
+ self.sources.extend(sources)
+ self.sources_need_sorting = True
+
+ def get_sources(self):
+ if self.sources_need_sorting:
+ self.sources = SCons.Util.uniquer_hashables(self.sources)
+ self.sources_need_sorting = False
+ return self.sources
def add_pre_action(self, action):
self.pre_actions.append(action)
def my_str(self):
env = self.get_build_env()
- get = lambda action, t=self.targets, s=self.sources, e=env: \
+ get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
action.genstring(t, s, e)
return string.join(map(get, self.get_action_list()), "\n")
except KeyError:
pass
env = self.get_build_env()
- get = lambda action, t=self.targets, s=self.sources, e=env: \
+ get = lambda action, t=self.targets, s=self.get_sources(), e=env: \
action.get_contents(t, s, e)
result = string.join(map(get, self.get_action_list()), "")
self._memo['get_contents'] = result
def scan_sources(self, scanner):
if self.sources:
- self.scan(scanner, self.sources)
+ self.scan(scanner, self.get_sources())
def scan(self, scanner, node_list):
"""Scan a list of this Executor's files (targets or sources) for
def get_missing_sources(self):
"""
"""
- return filter(lambda s: s.missing(), self.sources)
+ return filter(lambda s: s.missing(), self.get_sources())
def _get_unignored_sources_key(self, ignore=()):
return tuple(ignore)
except KeyError:
pass
- sourcelist = self.sources
+ sourcelist = self.get_sources()
if ignore:
- sourcelist = filter(lambda s, i=ignore: not s in i, sourcelist)
+ idict = {}
+ for i in ignore:
+ idict[i] = 1
+ sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist)
memo_dict[ignore] = sourcelist
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))
+ result.extend(act.get_implicit_deps(self.targets, self.get_sources(), build_env))
return result
x = SCons.Executor.Executor(a, env, [], t, ['s1', 's2'])
x.add_pre_action(pre_err)
x.add_post_action(post)
- x(t)
+ try:
+ x(t)
+ except SCons.Errors.BuildError:
+ pass
+ else:
+ raise Exception, "Did not catch expected BuildError"
assert result == ['pre_err'], result
del result[:]
x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
assert x.sources == ['s1', 's2'], x.sources
x.add_sources(['s1', 's2'])
+ assert x.sources == ['s1', 's2', 's1', 's2'], x.sources
+ x.add_sources(['s3', 's1', 's4'])
+ assert x.sources == ['s1', 's2', 's1', 's2', 's3', 's1', 's4'], x.sources
+
+ def test_get_sources(self):
+ """Test getting sources from an Executor"""
+ x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2'])
+ assert x.sources == ['s1', 's2'], x.sources
+ x.add_sources(['s1', 's2'])
+ x.get_sources()
assert x.sources == ['s1', 's2'], x.sources
x.add_sources(['s3', 's1', 's4'])
+ x.get_sources()
assert x.sources == ['s1', 's2', 's3', 's4'], x.sources
def test_add_pre_action(self):
def get_contents(self):
"""The contents of an alias is the concatenation
- of all the contents of its sources"""
- contents = map(lambda n: n.get_contents(), self.children())
- return string.join(contents, '')
+ of the content signatures of all its sources."""
+ childsigs = map(lambda n: n.get_csig(), self.children())
+ return string.join(childsigs, '')
def sconsign(self):
"""An Alias is not recorded in .sconsign files"""
return self.ninfo.csig
except AttributeError:
pass
-
+
contents = self.get_contents()
csig = SCons.Util.MD5signature(contents)
self.get_ninfo().csig = csig
class DummyNode:
def __init__(self, contents):
self.contents = contents
+ def get_csig(self):
+ return self.contents
def get_contents(self):
return self.contents
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import fnmatch
import os
import os.path
+import re
import shutil
import stat
import string
global Save_Strings
Save_Strings = val
+#
+# Avoid unnecessary function calls by recording a Boolean value that
+# tells us whether or not os.path.splitdrive() actually does anything
+# on this system, and therefore whether we need to bother calling it
+# when looking up path names in various methods below.
+#
+
+do_splitdrive = None
+
+def initialize_do_splitdrive():
+ global do_splitdrive
+ drive, path = os.path.splitdrive('X:/foo')
+ do_splitdrive = not not drive
+
+initialize_do_splitdrive()
+
+#
+
+needs_normpath_check = None
+
+def initialize_normpath_check():
+ """
+ Initialize the normpath_check regular expression.
+
+ This function is used by the unit tests to re-initialize the pattern
+ when testing for behavior with different values of os.sep.
+ """
+ global needs_normpath_check
+ if os.sep == '/':
+ pattern = r'.*/|\.$|\.\.$'
+ else:
+ pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
+ needs_normpath_check = re.compile(pattern)
+
+initialize_normpath_check()
+
#
# SCons.Action objects for interacting with the outside world.
#
return result
def _get_str(self):
+ global Save_Strings
if self.duplicate or self.is_derived():
return self.get_path()
- return self.srcnode().get_path()
+ srcnode = self.srcnode()
+ if srcnode.stat() is None and not self.stat() is None:
+ result = self.get_path()
+ else:
+ result = srcnode.get_path()
+ if not Save_Strings:
+ # We're not at the point where we're saving the string string
+ # representations of FS Nodes (because we haven't finished
+ # reading the SConscript files and need to have str() return
+ # things relative to them). That also means we can't yet
+ # cache values returned (or not returned) by stat(), since
+ # Python code in the SConscript files might still create
+ # or otherwise affect the on-disk file. So get rid of the
+ # values that the underlying stat() method saved.
+ try: del self._memo['stat']
+ except KeyError: pass
+ if not self is srcnode:
+ try: del srcnode._memo['stat']
+ except KeyError: pass
+ return result
rstr = __str__
corresponding to its source file. Otherwise, return
ourself.
"""
- dir=self.dir
- name=self.name
- while dir:
- if dir.srcdir:
- srcnode = dir.srcdir.Entry(name)
- srcnode.must_be_same(self.__class__)
- return srcnode
- name = dir.name + os.sep + name
- dir = dir.up()
+ srcdir_list = self.dir.srcdir_list()
+ if srcdir_list:
+ srcnode = srcdir_list[0].Entry(self.name)
+ srcnode.must_be_same(self.__class__)
+ return srcnode
return self
def get_path(self, dir=None):
def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
"""
- Generates a target entry that corresponds to this entry (usually
+ Generates a target entry that corresponds to this entry (usually
a source file) with the specified prefix and suffix.
Note that this method can be overridden dynamically for generated
self._memo['rentry'] = result
return result
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ return []
+
class Entry(Base):
"""This is the class for generic Node.FS entries--that is, things
that could be a File or a Dir, but we're just not sure yet.
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)
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ return self.disambiguate()._glob1(pattern, ondisk, source, strings)
+
# This is for later so we can differentiate between Entry the class and Entry
# the method of the FS class.
_classEntry = Entry
# return os.chdir(path)
def chmod(self, path, mode):
return os.chmod(path, mode)
+ def copy(self, src, dst):
+ return shutil.copy(src, dst)
def copy2(self, src, dst):
return shutil.copy2(src, dst)
def exists(self, path):
self.Top.tpath = '.'
self._cwd = self.Top
- DirNodeInfo.top = self.Top
- FileNodeInfo.top = self.Top
+ DirNodeInfo.fs = self
+ FileNodeInfo.fs = self
def set_SConstruct_dir(self, dir):
self.SConstruct_dir = dir
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.
+ to turn it into a fully 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.
+ interpreted relative to the top-level directory of this FS. '#'
+ is treated as a synonym for the top-level SConstruct directory,
+ much like '~' is treated as a synonym for the user's home
+ directory in a UNIX shell. So both '#foo' and '#/foo' refer
+ to the 'foo' subdirectory underneath the top-level SConstruct
+ directory.
If the path name is relative, then the path is looked up relative
to the specified directory, or the current directory (self._cwd,
return p
# str(p) in case it's something like a proxy object
p = str(p)
- drive, p = os.path.splitdrive(p)
+
+ initial_hash = (p[0:1] == '#')
+ if initial_hash:
+ # There was an initial '#', so we strip it and override
+ # whatever directory they may have specified with the
+ # top-level SConstruct directory.
+ p = p[1:]
+ directory = self.Top
+
+ if directory and not isinstance(directory, Dir):
+ directory = self.Dir(directory)
+
+ if do_splitdrive:
+ drive, p = os.path.splitdrive(p)
+ else:
+ drive = ''
if drive and not p:
- # A drive letter without a path...
+ # This causes a naked drive letter to be treated as a synonym
+ # for the root directory on that drive.
p = os.sep
- root = self.get_root(drive)
- elif os.path.isabs(p):
- # An absolute path...
+ absolute = os.path.isabs(p)
+
+ needs_normpath = needs_normpath_check.match(p)
+
+ if initial_hash or not absolute:
+ # This is a relative lookup, either to the top-level
+ # SConstruct directory (because of the initial '#') or to
+ # the current directory (the path name is not absolute).
+ # Add the string to the appropriate directory lookup path,
+ # after which the whole thing gets normalized.
+ if not directory:
+ directory = self._cwd
+ if p:
+ p = directory.labspath + '/' + p
+ else:
+ p = directory.labspath
+
+ if needs_normpath:
p = os.path.normpath(p)
+
+ if drive or absolute:
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)
+ if not directory:
+ directory = self._cwd
root = directory.root
+
if os.sep != '/':
p = string.replace(p, os.sep, '/')
return root._lookup_abs(p, fsclass, create)
"""
return self._lookup(name, directory, File, create)
- def Dir(self, name, directory = None, create = 1):
+ def Dir(self, name, directory = None, create = True):
"""Lookup or create a Dir 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,
message = fmt % string.join(map(str, targets))
return targets, message
+ def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
+ """
+ Globs
+
+ This is mainly a shim layer
+ """
+ if cwd is None:
+ cwd = self.getcwd()
+ return cwd.glob(pathname, ondisk, source, strings)
+
class DirNodeInfo(SCons.Node.NodeInfoBase):
# This should get reset by the FS initialization.
current_version_id = 1
- top = None
+ fs = None
def str_to_node(self, s):
- top = self.top
- if os.path.isabs(s):
- n = top.fs._lookup(s, top, Entry)
- else:
+ top = self.fs.Top
+ root = top.root
+ if do_splitdrive:
+ drive, s = os.path.splitdrive(s)
+ if drive:
+ root = self.fs.get_root(drive)
+ if not os.path.isabs(s):
s = top.labspath + '/' + s
- n = top.root._lookup_abs(s, Entry)
- return n
+ return root._lookup_abs(s, Entry)
class DirBuildInfo(SCons.Node.BuildInfoBase):
current_version_id = 1
+glob_magic_check = re.compile('[*?[]')
+
+def has_glob_magic(s):
+ return glob_magic_check.search(s) is not None
+
class Dir(Base):
"""A class for directories in a file system.
"""
"""
return self.fs.Entry(name, self)
- def Dir(self, name):
+ def Dir(self, name, create=True):
"""
Looks up or creates a directory node named 'name' relative to
this directory.
"""
- dir = self.fs.Dir(name, self)
+ dir = self.fs.Dir(name, self, create)
return dir
def File(self, name):
while dir:
for rep in dir.getRepositories():
result.append(rep.Dir(fname))
- fname = dir.name + os.sep + fname
+ if fname == '.':
+ fname = dir.name
+ else:
+ fname = dir.name + os.sep + fname
dir = dir.up()
self._memo['get_all_rdirs'] = result
def up(self):
return self.entries['..']
-# 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 _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.
+ """
+
+ # This complicated and expensive method, which constructs relative
+ # paths between arbitrary Node.FS objects, is no longer used
+ # by SCons itself. It was introduced to store dependency paths
+ # in .sconsign files relative to the target, but that ended up
+ # being significantly inefficient.
+ #
+ # We're continuing to support the method because some SConstruct
+ # files out there started using it when it was available, and
+ # we're all about backwards compatibility..
+
+ 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
dir = self
while dir:
if dir.srcdir:
- d = dir.srcdir.Dir(dirname)
- if d.is_under(dir):
- # Shouldn't source from something in the build path:
- # build_dir is probably under src_dir, in which case
- # we are reflecting.
- break
- result.append(d)
+ result.append(dir.srcdir.Dir(dirname))
dirname = dir.name + os.sep + dirname
dir = dir.up()
def srcdir_duplicate(self, name):
for dir in self.srcdir_list():
+ if self.is_under(dir):
+ # We shouldn't source from something in the build path;
+ # build_dir is probably under src_dir, in which case
+ # we are reflecting.
+ break
if dir.entry_exists_on_disk(name):
srcnode = dir.Entry(name).disambiguate()
if self.duplicate:
for dirname in filter(select_dirs, names):
entries[dirname].walk(func, arg)
+ def glob(self, pathname, ondisk=True, source=False, strings=False):
+ """
+ Returns a list of Nodes (or strings) matching a specified
+ pathname pattern.
+
+ Pathname patterns follow UNIX shell semantics: * matches
+ any-length strings of any characters, ? matches any character,
+ and [] can enclose lists or ranges of characters. Matches do
+ not span directory separators.
+
+ The matches take into account Repositories, returning local
+ Nodes if a corresponding entry exists in a Repository (either
+ an in-memory Node or something on disk).
+
+ By defafult, the glob() function matches entries that exist
+ on-disk, in addition to in-memory Nodes. Setting the "ondisk"
+ argument to False (or some other non-true value) causes the glob()
+ function to only match in-memory Nodes. The default behavior is
+ to return both the on-disk and in-memory Nodes.
+
+ The "source" argument, when true, specifies that corresponding
+ source Nodes must be returned if you're globbing in a build
+ directory (initialized with BuildDir()). The default behavior
+ is to return Nodes local to the BuildDir().
+
+ The "strings" argument, when true, returns the matches as strings,
+ not Nodes. The strings are path names relative to this directory.
+
+ The underlying algorithm is adapted from the glob.glob() function
+ in the Python library (but heavily modified), and uses fnmatch()
+ under the covers.
+ """
+ dirname, basename = os.path.split(pathname)
+ if not dirname:
+ return self._glob1(basename, ondisk, source, strings)
+ if has_glob_magic(dirname):
+ list = self.glob(dirname, ondisk, source, strings=False)
+ else:
+ list = [self.Dir(dirname, create=True)]
+ result = []
+ for dir in list:
+ r = dir._glob1(basename, ondisk, source, strings)
+ if strings:
+ r = map(lambda x, d=str(dir): os.path.join(d, x), r)
+ result.extend(r)
+ return result
+
+ def _glob1(self, pattern, ondisk=True, source=False, strings=False):
+ """
+ Globs for and returns a list of entry names matching a single
+ pattern in this directory.
+
+ This searches any repositories and source directories for
+ corresponding entries and returns a Node (or string) relative
+ to the current directory if an entry is found anywhere.
+
+ TODO: handle pattern with no wildcard
+ """
+ search_dir_list = self.get_all_rdirs()
+ for srcdir in self.srcdir_list():
+ search_dir_list.extend(srcdir.get_all_rdirs())
+
+ names = []
+ for dir in search_dir_list:
+ # We use the .name attribute from the Node because the keys of
+ # the dir.entries dictionary are normalized (that is, all upper
+ # case) on case-insensitive systems like Windows.
+ #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ]
+ entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
+ node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
+ names.extend(node_names)
+ if ondisk:
+ try:
+ disk_names = os.listdir(dir.abspath)
+ except os.error:
+ pass
+ else:
+ names.extend(disk_names)
+ if not strings:
+ # We're going to return corresponding Nodes in
+ # the local directory, so we need to make sure
+ # those Nodes exist. We only want to create
+ # Nodes for the entries that will match the
+ # specified pattern, though, which means we
+ # need to filter the list here, even though
+ # the overall list will also be filtered later,
+ # after we exit this loop.
+ if pattern[0] != '.':
+ #disk_names = [ d for d in disk_names if d[0] != '.' ]
+ disk_names = filter(lambda x: x[0] != '.', disk_names)
+ disk_names = fnmatch.filter(disk_names, pattern)
+ rep_nodes = map(dir.Entry, disk_names)
+ #rep_nodes = [ n.disambiguate() for n in rep_nodes ]
+ rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
+ for node, name in zip(rep_nodes, disk_names):
+ n = self.Entry(name)
+ if n.__class__ != node.__class__:
+ n.__class__ = node.__class__
+ n._morph()
+
+ names = set(names)
+ if pattern[0] != '.':
+ #names = [ n for n in names if n[0] != '.' ]
+ names = filter(lambda x: x[0] != '.', names)
+ names = fnmatch.filter(names, pattern)
+
+ if strings:
+ return names
+
+ #return [ self.entries[_my_normcase(n)] for n in names ]
+ return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
+
class RootDir(Dir):
"""A class for the root directory of a file system.
field_list = ['csig', 'timestamp', 'size']
# This should get reset by the FS initialization.
- top = None
+ fs = None
def str_to_node(self, s):
- top = self.top
- if os.path.isabs(s):
- n = top.fs._lookup(s, top, Entry)
- else:
+ top = self.fs.Top
+ root = top.root
+ if do_splitdrive:
+ drive, s = os.path.splitdrive(s)
+ if drive:
+ root = self.fs.get_root(drive)
+ if not os.path.isabs(s):
s = top.labspath + '/' + s
- n = top.root._lookup_abs(s, Entry)
- return n
+ return root._lookup_abs(s, Entry)
class FileBuildInfo(SCons.Node.BuildInfoBase):
current_version_id = 1
the SConscript directory of this file."""
return self.cwd.Entry(name)
- def Dir(self, name):
+ def Dir(self, name, create=True):
"""Create a directory node named 'name' relative to
the SConscript directory of this file."""
- return self.cwd.Dir(name)
+ return self.cwd.Dir(name, create)
def Dirs(self, pathlist):
"""Create a list of directories relative to the SConscript
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)
def _rmv_existing(self):
self.clear_memoized_values()
- Unlink(self, [], None)
+ e = Unlink(self, [], None)
+ if isinstance(e, SCons.Errors.BuildError):
+ raise e
#
# Taskmaster interface subsystem
def do_duplicate(self, src):
self._createDir()
- try:
- Unlink(self, None, None)
- except SCons.Errors.BuildError:
- pass
- try:
- Link(self, src, None)
- except SCons.Errors.BuildError, e:
+ Unlink(self, None, None)
+ e = Link(self, src, None)
+ if isinstance(e, SCons.Errors.BuildError):
desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
raise SCons.Errors.StopError, desc
self.linked = 1
# 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
+ csig = ''
else:
csig = SCons.Util.MD5signature(contents)
# ...and it's even up-to-date...
if self._local:
# ...and they'd like a local copy.
- LocalCopy(self, r, None)
+ e = LocalCopy(self, r, None)
+ if isinstance(e, SCons.Errors.BuildError):
+ raise
self.store_info()
if T: Trace(' 1\n')
return 1
def __init__(self):
self._memo = {}
+ def filedir_lookup(self, p, fd=None):
+ """
+ A helper method for find_file() that looks up a directory for
+ a file we're trying to find. This only creates the Dir Node if
+ it exists on-disk, since if the directory doesn't exist we know
+ we won't find any files in it... :-)
+
+ It would be more compact to just use this as a nested function
+ with a default keyword argument (see the commented-out version
+ below), but that doesn't work unless you have nested scopes,
+ so we define it here just this works work under Python 1.5.2.
+ """
+ if fd is None:
+ fd = self.default_filedir
+ dir, name = os.path.split(fd)
+ drive, d = os.path.splitdrive(dir)
+ if d in ('/', os.sep):
+ return p
+ if dir:
+ p = self.filedir_lookup(p, dir)
+ if not p:
+ return None
+ norm_name = _my_normcase(name)
+ try:
+ node = p.entries[norm_name]
+ except KeyError:
+ return p.dir_on_disk(name)
+ # Once we move to Python 2.2 we can do:
+ #if isinstance(node, (Dir, Entry)):
+ if isinstance(node, Dir) or isinstance(node, Entry):
+ return node
+ return None
+
def _find_file_key(self, filename, paths, verbose=None):
return (filename, paths)
filedir, filename = os.path.split(filename)
if filedir:
- def filedir_lookup(p, fd=filedir):
- try:
- return p.Dir(fd)
- except TypeError:
- # We tried to look up a Dir, but it seems there's
- # already a File (or something else) there. No big.
- return None
- paths = filter(None, map(filedir_lookup, paths))
+ # More compact code that we can't use until we drop
+ # support for Python 1.5.2:
+ #
+ #def filedir_lookup(p, fd=filedir):
+ # """
+ # A helper function that looks up a directory for a file
+ # we're trying to find. This only creates the Dir Node
+ # if it exists on-disk, since if the directory doesn't
+ # exist we know we won't find any files in it... :-)
+ # """
+ # dir, name = os.path.split(fd)
+ # if dir:
+ # p = filedir_lookup(p, dir)
+ # if not p:
+ # return None
+ # norm_name = _my_normcase(name)
+ # try:
+ # node = p.entries[norm_name]
+ # except KeyError:
+ # return p.dir_on_disk(name)
+ # # Once we move to Python 2.2 we can do:
+ # #if isinstance(node, (Dir, Entry)):
+ # if isinstance(node, Dir) or isinstance(node, Entry):
+ # return node
+ # return None
+ #paths = filter(None, map(filedir_lookup, paths))
+
+ self.default_filedir = filedir
+ paths = filter(None, map(self.filedir_lookup, paths))
result = None
for dir in paths:
assert not f7.exists()
assert f7.rexists()
- assert f7.rfile().path == os.path.normpath(test.workpath('rep1/build/var1/test2.out')),\
- f7.rfile().path
+ r = f7.rfile().path
+ expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out'))
+ assert r == expect, (repr(r), repr(expect))
assert not f8.exists()
assert f8.rexists()
'work/src/b1/b2',
'work/src/b1/b2/b1',
'work/src/b1/b2/b1/b2',
+ 'work/src/b1/b2/b1/b2/b1',
+ 'work/src/b1/b2/b1/b2/b1/b2',
]
srcnode_map = {
'work/src/b1/b2/b1/f' : 'work/src/b1/f',
'work/src/b1/b2/b1/b2' : 'work/src/b1/b2',
'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f',
+ 'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1',
+ 'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f',
+ 'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2',
+ 'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f',
}
alter_map = {
drive, path = os.path.splitdrive(os.getcwd())
+ def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive):
+ dir = fileSys.Dir(string.replace(lpath, '/', sep))
+
+ if os.sep != '/':
+ path_ = string.replace(path_, '/', os.sep)
+ abspath_ = string.replace(abspath_, '/', os.sep)
+ up_path_ = string.replace(up_path_, '/', os.sep)
+
+ def strip_slash(p, drive=drive):
+ if p[-1] == os.sep and len(p) > 1:
+ p = p[:-1]
+ if p[0] == os.sep:
+ p = drive + p
+ return p
+ path = strip_slash(path_)
+ abspath = strip_slash(abspath_)
+ up_path = strip_slash(up_path_)
+ name = string.split(abspath, os.sep)[-1]
+
+ assert dir.name == name, \
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
+ assert dir.path == path, \
+ "dir.path %s != expected path %s" % \
+ (dir.path, path)
+ assert str(dir) == path, \
+ "str(dir) %s != expected path %s" % \
+ (str(dir), path)
+ assert dir.get_abspath() == abspath, \
+ "dir.abspath %s != expected absolute path %s" % \
+ (dir.get_abspath(), abspath)
+ assert dir.up().path == up_path, \
+ "dir.up().path %s != expected parent path %s" % \
+ (dir.up().path, up_path)
+
for sep in seps:
- def Dir_test(lpath, path_, abspath_, up_path_, fileSys=fs, s=sep, drive=drive):
- dir = fileSys.Dir(string.replace(lpath, '/', s))
-
- if os.sep != '/':
- path_ = string.replace(path_, '/', os.sep)
- abspath_ = string.replace(abspath_, '/', os.sep)
- up_path_ = string.replace(up_path_, '/', os.sep)
-
- def strip_slash(p, drive=drive):
- if p[-1] == os.sep and len(p) > 1:
- p = p[:-1]
- if p[0] == os.sep:
- p = drive + p
- return p
- path = strip_slash(path_)
- abspath = strip_slash(abspath_)
- up_path = strip_slash(up_path_)
- name = string.split(abspath, os.sep)[-1]
-
- assert dir.name == name, \
- "dir.name %s != expected name %s" % \
- (dir.name, name)
- assert dir.path == path, \
- "dir.path %s != expected path %s" % \
- (dir.path, path)
- assert str(dir) == path, \
- "str(dir) %s != expected path %s" % \
- (str(dir), path)
- assert dir.get_abspath() == abspath, \
- "dir.abspath %s != expected absolute path %s" % \
- (dir.get_abspath(), abspath)
- assert dir.up().path == up_path, \
- "dir.up().path %s != expected parent path %s" % \
- (dir.up().path, up_path)
+ def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test):
+ return func(lpath, path_, abspath_, up_path_, sep)
+ Dir_test('', './', sub_dir, sub)
Dir_test('foo', 'foo/', sub_dir_foo, './')
Dir_test('foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/')
Dir_test('/foo', '/foo/', '/foo/', '/')
f.get_string(0)
assert f.get_string(1) == 'baz', f.get_string(1)
+ def test_drive_letters(self):
+ """Test drive-letter look-ups"""
+
+ test = self.test
+
+ test.subdir('sub', ['sub', 'dir'])
+
+ def drive_workpath(drive, dirs, test=test):
+ x = apply(test.workpath, dirs)
+ drive, path = os.path.splitdrive(x)
+ return 'X:' + path
+
+ wp = drive_workpath('X:', [''])
+
+ if wp[-1] in (os.sep, '/'):
+ tmp = os.path.split(wp[:-1])[0]
+ else:
+ tmp = os.path.split(wp)[0]
+
+ parent_tmp = os.path.split(tmp)[0]
+ if parent_tmp == 'X:':
+ parent_tmp = 'X:' + os.sep
+
+ tmp_foo = os.path.join(tmp, 'foo')
+
+ foo = drive_workpath('X:', ['foo'])
+ foo_bar = drive_workpath('X:', ['foo', 'bar'])
+ sub = drive_workpath('X:', ['sub', ''])
+ sub_dir = drive_workpath('X:', ['sub', 'dir', ''])
+ sub_dir_foo = drive_workpath('X:', ['sub', 'dir', 'foo', ''])
+ sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', ''])
+ sub_foo = drive_workpath('X:', ['sub', 'foo', ''])
+
+ fs = SCons.Node.FS.FS()
+
+ seps = [os.sep]
+ if os.sep != '/':
+ seps = seps + ['/']
+
+ def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs):
+ dir = fileSys.Dir(string.replace(lpath, '/', sep))
+
+ if os.sep != '/':
+ path_ = string.replace(path_, '/', os.sep)
+ up_path_ = string.replace(up_path_, '/', os.sep)
+
+ def strip_slash(p):
+ if p[-1] == os.sep and len(p) > 3:
+ p = p[:-1]
+ return p
+ path = strip_slash(path_)
+ up_path = strip_slash(up_path_)
+ name = string.split(path, os.sep)[-1]
+
+ assert dir.name == name, \
+ "dir.name %s != expected name %s" % \
+ (dir.name, name)
+ assert dir.path == path, \
+ "dir.path %s != expected path %s" % \
+ (dir.path, path)
+ assert str(dir) == path, \
+ "str(dir) %s != expected path %s" % \
+ (str(dir), path)
+ assert dir.up().path == up_path, \
+ "dir.up().path %s != expected parent path %s" % \
+ (dir.up().path, up_path)
+
+ save_os_path = os.path
+ save_os_sep = os.sep
+ try:
+ import ntpath
+ os.path = ntpath
+ os.sep = '\\'
+ SCons.Node.FS.initialize_do_splitdrive()
+ SCons.Node.FS.initialize_normpath_check()
+
+ for sep in seps:
+
+ def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
+ return func(lpath, path_, up_path_, sep)
+
+ Dir_test('#X:', wp, tmp)
+ Dir_test('X:foo', foo, wp)
+ Dir_test('X:foo/bar', foo_bar, foo)
+ Dir_test('X:/foo', 'X:/foo', 'X:/')
+ Dir_test('X:/foo/bar', 'X:/foo/bar/', 'X:/foo/')
+ Dir_test('X:..', tmp, parent_tmp)
+ Dir_test('X:foo/..', wp, tmp)
+ Dir_test('X:../foo', tmp_foo, tmp)
+ Dir_test('X:.', wp, tmp)
+ Dir_test('X:./.', wp, tmp)
+ Dir_test('X:foo/./bar', foo_bar, foo)
+ Dir_test('#X:../foo', tmp_foo, tmp)
+ Dir_test('#X:/../foo', tmp_foo, tmp)
+ Dir_test('#X:foo/bar', foo_bar, foo)
+ Dir_test('#X:/foo/bar', foo_bar, foo)
+ Dir_test('#X:/', wp, tmp)
+ finally:
+ os.path = save_os_path
+ os.sep = save_os_sep
+ SCons.Node.FS.initialize_do_splitdrive()
+ SCons.Node.FS.initialize_normpath_check()
+
def test_target_from_source(self):
"""Test the method for generating target nodes from sources"""
fs = self.fs
above_path = apply(os.path.join, ['..']*len(dirs) + ['above'])
above = d2.Dir(above_path)
- # 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):
+ def test_rel_path(self):
"""Test the rel_path() method"""
test = self.test
fs = self.fs
check(s, ['src/b1'])
s = b1_b2_b1_b2.srcdir_list()
- check(s, [])
+ check(s, ['src/b1/b2'])
s = b1_b2_b1_b2_sub.srcdir_list()
- check(s, [])
+ check(s, ['src/b1/b2/sub'])
def test_srcdir_duplicate(self):
"""Test the Dir.srcdir_duplicate() method
+class GlobTestCase(_tempdirTestCase):
+ def setUp(self):
+ _tempdirTestCase.setUp(self)
+
+ fs = SCons.Node.FS.FS()
+ self.fs = fs
+
+ # Make entries on disk that will not have Nodes, so we can verify
+ # the behavior of looking for things on disk.
+ self.test.write('disk-aaa', "disk-aaa\n")
+ self.test.write('disk-bbb', "disk-bbb\n")
+ self.test.write('disk-ccc', "disk-ccc\n")
+ self.test.subdir('disk-sub')
+ self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n")
+ self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n")
+ self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n")
+
+ # Make some entries that have both Nodes and on-disk entries,
+ # so we can verify what we do with
+ self.test.write('both-aaa', "both-aaa\n")
+ self.test.write('both-bbb', "both-bbb\n")
+ self.test.write('both-ccc', "both-ccc\n")
+ self.test.subdir('both-sub1')
+ self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n")
+ self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n")
+ self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n")
+ self.test.subdir('both-sub2')
+ self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n")
+ self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n")
+ self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n")
+
+ self.both_aaa = fs.File('both-aaa')
+ self.both_bbb = fs.File('both-bbb')
+ self.both_ccc = fs.File('both-ccc')
+ self.both_sub1 = fs.Dir('both-sub1')
+ self.both_sub1_both_ddd = self.both_sub1.File('both-ddd')
+ self.both_sub1_both_eee = self.both_sub1.File('both-eee')
+ self.both_sub1_both_fff = self.both_sub1.File('both-fff')
+ self.both_sub2 = fs.Dir('both-sub2')
+ self.both_sub2_both_ddd = self.both_sub2.File('both-ddd')
+ self.both_sub2_both_eee = self.both_sub2.File('both-eee')
+ self.both_sub2_both_fff = self.both_sub2.File('both-fff')
+
+ # Make various Nodes (that don't have on-disk entries) so we
+ # can verify how we match them.
+ self.ggg = fs.File('ggg')
+ self.hhh = fs.File('hhh')
+ self.iii = fs.File('iii')
+ self.subdir1 = fs.Dir('subdir1')
+ self.subdir1_jjj = self.subdir1.File('jjj')
+ self.subdir1_kkk = self.subdir1.File('kkk')
+ self.subdir1_lll = self.subdir1.File('lll')
+ self.subdir2 = fs.Dir('subdir2')
+ self.subdir2_jjj = self.subdir2.File('jjj')
+ self.subdir2_kkk = self.subdir2.File('kkk')
+ self.subdir2_lll = self.subdir2.File('lll')
+ self.sub = fs.Dir('sub')
+ self.sub_dir3 = self.sub.Dir('dir3')
+ self.sub_dir3_jjj = self.sub_dir3.File('jjj')
+ self.sub_dir3_kkk = self.sub_dir3.File('kkk')
+ self.sub_dir3_lll = self.sub_dir3.File('lll')
+
+
+ def do_cases(self, cases, **kwargs):
+
+ # First, execute all of the cases with string=True and verify
+ # that we get the expected strings returned. We do this first
+ # so the Glob() calls don't add Nodes to the self.fs file system
+ # hierarchy.
+
+ import copy
+ strings_kwargs = copy.copy(kwargs)
+ strings_kwargs['strings'] = True
+ for input, string_expect, node_expect in cases:
+ r = apply(self.fs.Glob, (input,), strings_kwargs)
+ r.sort()
+ assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r)
+
+ # Now execute all of the cases without string=True and look for
+ # the expected Nodes to be returned. If we don't have a list of
+ # actual expected Nodes, that means we're expecting a search for
+ # on-disk-only files to have returned some newly-created nodes.
+ # Verify those by running the list through str() before comparing
+ # them with the expected list of strings.
+ for input, string_expect, node_expect in cases:
+ r = apply(self.fs.Glob, (input,), kwargs)
+ if node_expect:
+ r.sort(lambda a,b: cmp(a.path, b.path))
+ result = node_expect
+ else:
+ r = map(str, r)
+ r.sort()
+ result = string_expect
+ assert r == result, "Glob(%s) expected %s, got %s" % (input, map(str, result), map(str, r))
+
+ def test_exact_match(self):
+ """Test globbing for exact Node matches"""
+ join = os.path.join
+
+ cases = (
+ ('ggg', ['ggg'], [self.ggg]),
+
+ ('subdir1', ['subdir1'], [self.subdir1]),
+
+ ('subdir1/jjj', [join('subdir1', 'jjj')], [self.subdir1_jjj]),
+
+ ('disk-aaa', ['disk-aaa'], None),
+
+ ('disk-sub', ['disk-sub'], None),
+
+ ('both-aaa', ['both-aaa'], []),
+ )
+
+ self.do_cases(cases)
+
+ def test_subdir_matches(self):
+ """Test globbing for exact Node matches in subdirectories"""
+ join = os.path.join
+
+ cases = (
+ ('*/jjj',
+ [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+ [self.subdir1_jjj, self.subdir2_jjj]),
+
+ ('*/disk-ddd',
+ [join('disk-sub', 'disk-ddd')],
+ None),
+ )
+
+ self.do_cases(cases)
+
+ def test_asterisk(self):
+ """Test globbing for simple asterisk Node matches"""
+ cases = (
+ ('h*',
+ ['hhh'],
+ [self.hhh]),
+
+ ('*',
+ ['both-aaa', 'both-bbb', 'both-ccc',
+ 'both-sub1', 'both-sub2',
+ 'ggg', 'hhh', 'iii',
+ 'sub', 'subdir1', 'subdir2'],
+ [self.both_aaa, self.both_bbb, self.both_ccc,
+ self.both_sub1, self.both_sub2,
+ self.ggg, self.hhh, self.iii,
+ self.sub, self.subdir1, self.subdir2]),
+ )
+
+ self.do_cases(cases, ondisk=False)
+
+ cases = (
+ ('disk-b*',
+ ['disk-bbb'],
+ None),
+
+ ('*',
+ ['both-aaa', 'both-bbb', 'both-ccc', 'both-sub1', 'both-sub2',
+ 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub',
+ 'ggg', 'hhh', 'iii',
+ 'sub', 'subdir1', 'subdir2'],
+ None),
+ )
+
+ self.do_cases(cases)
+
+ def test_question_mark(self):
+ """Test globbing for simple question-mark Node matches"""
+ join = os.path.join
+
+ cases = (
+ ('ii?',
+ ['iii'],
+ [self.iii]),
+
+ ('both-sub?/both-eee',
+ [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')],
+ [self.both_sub1_both_eee, self.both_sub2_both_eee]),
+
+ ('subdir?/jjj',
+ [join('subdir1', 'jjj'), join('subdir2', 'jjj')],
+ [self.subdir1_jjj, self.subdir2_jjj]),
+
+ ('disk-cc?',
+ ['disk-ccc'],
+ None),
+ )
+
+ self.do_cases(cases)
+
+ def test_does_not_exist(self):
+ """Test globbing for things that don't exist"""
+
+ cases = (
+ ('does_not_exist', [], []),
+ ('no_subdir/*', [], []),
+ ('subdir?/no_file', [], []),
+ )
+
+ self.do_cases(cases)
+
+ def test_subdir_asterisk(self):
+ """Test globbing for asterisk Node matches in subdirectories"""
+ join = os.path.join
+
+ cases = (
+ ('*/k*',
+ [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+ [self.subdir1_kkk, self.subdir2_kkk]),
+
+ ('both-sub?/*',
+ [join('both-sub1', 'both-ddd'),
+ join('both-sub1', 'both-eee'),
+ join('both-sub1', 'both-fff'),
+ join('both-sub2', 'both-ddd'),
+ join('both-sub2', 'both-eee'),
+ join('both-sub2', 'both-fff')],
+ [self.both_sub1_both_ddd, self.both_sub1_both_eee, self.both_sub1_both_fff,
+ self.both_sub2_both_ddd, self.both_sub2_both_eee, self.both_sub2_both_fff],
+ ),
+
+ ('subdir?/*',
+ [join('subdir1', 'jjj'),
+ join('subdir1', 'kkk'),
+ join('subdir1', 'lll'),
+ join('subdir2', 'jjj'),
+ join('subdir2', 'kkk'),
+ join('subdir2', 'lll')],
+ [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll,
+ self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]),
+
+ ('sub/*/*',
+ [join('sub', 'dir3', 'jjj'),
+ join('sub', 'dir3', 'kkk'),
+ join('sub', 'dir3', 'lll')],
+ [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]),
+
+ ('*/k*',
+ [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+ None),
+
+ ('subdir?/*',
+ [join('subdir1', 'jjj'),
+ join('subdir1', 'kkk'),
+ join('subdir1', 'lll'),
+ join('subdir2', 'jjj'),
+ join('subdir2', 'kkk'),
+ join('subdir2', 'lll')],
+ None),
+
+ ('sub/*/*',
+ [join('sub', 'dir3', 'jjj'),
+ join('sub', 'dir3', 'kkk'),
+ join('sub', 'dir3', 'lll')],
+ None),
+ )
+
+ self.do_cases(cases)
+
+ def test_subdir_question(self):
+ """Test globbing for question-mark Node matches in subdirectories"""
+ join = os.path.join
+
+ cases = (
+ ('*/?kk',
+ [join('subdir1', 'kkk'), join('subdir2', 'kkk')],
+ [self.subdir1_kkk, self.subdir2_kkk]),
+
+ ('subdir?/l?l',
+ [join('subdir1', 'lll'), join('subdir2', 'lll')],
+ [self.subdir1_lll, self.subdir2_lll]),
+
+ ('*/disk-?ff',
+ [join('disk-sub', 'disk-fff')],
+ None),
+
+ ('subdir?/l?l',
+ [join('subdir1', 'lll'), join('subdir2', 'lll')],
+ None),
+ )
+
+ self.do_cases(cases)
+
+
+
class RepositoryTestCase(_tempdirTestCase):
def setUp(self):
fs = SCons.Node.FS.FS(test.workpath(''))
d = fs.Dir('sub', '.')
- assert str(d) == 'sub'
+ assert str(d) == 'sub', str(d)
assert d.exists()
f = fs.File('file', 'sub')
assert str(f) == os.path.join('sub', 'file')
FileBuildInfoTestCase,
FileNodeInfoTestCase,
FSTestCase,
+ GlobTestCase,
RepositoryTestCase,
]
for tclass in tclasses:
self.depends_dict = {}
self.ignore = [] # dependencies to ignore
self.ignore_dict = {}
+ self.prerequisites = SCons.Util.UniqueList()
self.implicit = None # implicit (scanned) dependencies (None means not scanned yet)
self.waiting_parents = {}
self.waiting_s_e = {}
in built().
"""
- executor = self.get_executor()
- stat = apply(executor, (self,), kw)
- if stat:
- msg = "Error %d" % stat
- raise SCons.Errors.BuildError(node=self, errstr=msg)
+ try:
+ apply(self.get_executor(), (self,), kw)
+ except SCons.Errors.BuildError, e:
+ e.node = self
+ raise
def built(self):
"""Called just after this node is successfully built."""
return
build_env = self.get_build_env()
+ executor = self.get_executor()
# Here's where we implement --implicit-cache.
if implicit_cache and not implicit_deps_changed:
# 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)
+
+ # Update all of the targets with them. This
+ # essentially short-circuits an N*M scan of the
+ # sources for each individual target, which is a hell
+ # of a lot more efficient.
+ for tgt in executor.targets:
+ tgt.add_to_implicit(implicit)
+
if implicit_deps_unchanged or self.is_up_to_date():
return
# one of this node's sources has changed,
self._children_reset()
self.del_binfo()
- executor = self.get_executor()
-
# Have the executor scan the sources.
executor.scan_sources(self.builder.source_scanner)
s = str(e)
raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e)))
+ def add_prerequisite(self, prerequisite):
+ """Adds prerequisites"""
+ self.prerequisites.extend(prerequisite)
+ self._children_reset()
+
def add_ignore(self, depend):
"""Adds dependencies to ignore."""
try:
lines = ["%s:\n" % preamble] + lines
return string.join(lines, ' '*11)
-l = [1]
-ul = UserList.UserList([2])
try:
- l.extend(ul)
+ [].extend(UserList.UserList([]))
except TypeError:
+ # Python 1.5.2 doesn't allow a list to be extended by list-like
+ # objects (such as UserList instances), so just punt and use
+ # real lists.
def NodeList(l):
return l
else:
class NodeList(UserList.UserList):
def __str__(self):
return str(map(str, self.data))
-del l
-del ul
def get_children(node, parent): return node.children()
def ignore_cycle(node, stack): pass
assert text == expectAlpha, text
-
+
+class UnknownOptionsTestCase(unittest.TestCase):
+
+ def test_unknown(self):
+ """Test the UnknownOptions() method"""
+ opts = SCons.Options.Options()
+ opts.Add('ANSWER',
+ 'THE answer to THE question',
+ "42")
+
+ args = {
+ 'ANSWER' : 'answer',
+ 'UNKNOWN' : 'unknown',
+ }
+
+ env = Environment()
+ opts.Update(env, args)
+
+ r = opts.UnknownOptions()
+ assert r == {'UNKNOWN' : 'unknown'}, r
+ assert env['ANSWER'] == 'answer', env['ANSWER']
+
+
+
if __name__ == "__main__":
- suite = unittest.makeSuite(OptionsTestCase, 'test_')
+ suite = unittest.TestSuite()
+ tclasses = [ OptionsTestCase,
+ UnknownOptionsTestCase ]
+ for tclass in tclasses:
+ names = unittest.getTestCaseNames(tclass, 'test_')
+ suite.addTests(map(tclass, names))
if not unittest.TextTestRunner().run(suite).wasSuccessful():
sys.exit(1)
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import SCons.compat
+
import os.path
import string
+import sys
import SCons.Errors
import SCons.Util
else:
files = []
self.files = files
+ self.unknown = {}
# create the singleton instance
if is_global:
# next set the value specified in the options file
for filename in self.files:
if os.path.exists(filename):
- execfile(filename, values)
-
- # finally set the values specified on the command line
+ dir = os.path.split(os.path.abspath(filename))[0]
+ if dir:
+ sys.path.insert(0, dir)
+ try:
+ values['__name__'] = filename
+ execfile(filename, {}, values)
+ finally:
+ if dir:
+ del sys.path[0]
+ del values['__name__']
+
+ # set the values specified on the command line
if args is None:
args = self.args
for arg, value in args.items():
- for option in self.options:
- if arg in option.aliases + [ option.key ]:
- values[option.key]=value
+ added = False
+ for option in self.options:
+ if arg in option.aliases + [ option.key ]:
+ values[option.key] = value
+ added = True
+ if not added:
+ self.unknown[arg] = value
# put the variables in the environment:
# (don't copy over variables that are not declared as options)
if option.validator and values.has_key(option.key):
option.validator(option.key, env.subst('${%s}'%option.key), env)
+ def UnknownOptions(self):
+ """
+ Returns any options in the specified arguments lists that
+ were not known, declared options in this object.
+ """
+ return self.unknown
+
def Save(self, filename, env):
"""
Saves all the options in the given file. This file can
from SCons.Platform.posix import exitvalmap
from SCons.Platform import TempFileMunge
-
-# XXX See note below about why importing SCons.Action should be
-# eventually refactored.
-import SCons.Action
import SCons.Util
+
+try:
+ import msvcrt
+ import win32api
+ import win32con
+
+ msvcrt.get_osfhandle
+ win32api.SetHandleInformation
+ win32con.HANDLE_FLAG_INHERIT
+except ImportError:
+ parallel_msg = \
+ "you do not seem to have the pywin32 extensions installed;\n" + \
+ "\tparallel (-j) builds may not work reliably with open Python files."
+except AttributeError:
+ parallel_msg = \
+ "your pywin32 extensions do not support file handle operations;\n" + \
+ "\tparallel (-j) builds may not work reliably with open Python files."
+else:
+ parallel_msg = None
+
+ import __builtin__
+
+ _builtin_file = __builtin__.file
+ _builtin_open = __builtin__.open
+
+ def _scons_file(*args, **kw):
+ fp = apply(_builtin_file, args, kw)
+ win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
+ win32con.HANDLE_FLAG_INHERIT,
+ 0)
+ return fp
+
+ def _scons_open(*args, **kw):
+ fp = apply(_builtin_open, args, kw)
+ win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
+ win32con.HANDLE_FLAG_INHERIT,
+ 0)
+ return fp
+
+ __builtin__.file = _scons_file
+ __builtin__.open = _scons_open
+
+
+
# The upshot of all this is that, if you are using Python 1.5.2,
# you had better have cmd or command.com in your PATH when you run
# scons.
SCons.Conftest.LogInputFiles = 0
SCons.Conftest.LogErrorMessages = 0
+# Set
+build_type = None
+build_types = ['clean', 'help']
+
+def SetBuildType(type):
+ global build_type
+ build_type = type
+
# to be set, if we are in dry-run mode
dryrun = 0
sconsign.set_entry(t.name, sconsign_entry)
sconsign.merge()
-class SConf:
+class SConfBase:
"""This is simply a class to represent a configure context. After
creating a SConf object, you can call any tests. After finished with your
tests, be sure to call the Finish() method, which returns the modified
default_tests = {
'CheckFunc' : CheckFunc,
'CheckType' : CheckType,
+ 'CheckTypeSize' : CheckTypeSize,
'CheckHeader' : CheckHeader,
'CheckCHeader' : CheckCHeader,
'CheckCXXHeader' : CheckCXXHeader,
def AddTest(self, test_name, test_instance):
"""Adds test_class to this SConf instance. It can be called with
self.test_name(...)"""
- setattr(self, test_name, SConf.TestWrapper(test_instance, self))
+ setattr(self, test_name, SConfBase.TestWrapper(test_instance, self))
def AddTests(self, tests):
"""Adds all the tests given in the tests dictionary to this SConf
#### End of stuff used by Conftest.py.
+def SConf(*args, **kw):
+ if kw.get(build_type, True):
+ kw['_depth'] = kw.get('_depth', 0) + 1
+ for bt in build_types:
+ try:
+ del kw[bt]
+ except KeyError:
+ pass
+ return apply(SConfBase, args, kw)
+ else:
+ return SCons.Util.Null()
+
+
def CheckFunc(context, function_name, header = None, language = None):
res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language)
context.did_show_result = 1
context.did_show_result = 1
return not res
+def CheckTypeSize(context, type_name, includes = "", language = None, expect = None):
+ res = SCons.Conftest.CheckTypeSize(context, type_name,
+ header = includes, language = language,
+ expect = expect)
+ context.did_show_result = 1
+ return res
+
def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
# used by CheckHeader and CheckLibWithHeader to produce C - #include
# statements from the specified header (list)
# 'TryLink'), so we are aware of reloading modules.
def checks(self, sconf, TryFuncString):
- TryFunc = self.SConf.SConf.__dict__[TryFuncString]
+ TryFunc = self.SConf.SConfBase.__dict__[TryFuncString]
res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" )
res2 = TryFunc( sconf,
'#include "no_std_header.h"\nint main() {return 0; }\n',
finally:
sconf.Finish()
+ def test_CheckTypeSize(self):
+ """Test SConf.CheckTypeSize()
+ """
+ self._resetSConfState()
+ sconf = self.SConf.SConf(self.scons_env,
+ conf_dir=self.test.workpath('config.tests'),
+ log_file=self.test.workpath('config.log'))
+ try:
+ # CheckTypeSize()
+
+ # In ANSI C, sizeof(char) == 1.
+ r = sconf.CheckTypeSize('char', expect = 1)
+ assert r == 1, "sizeof(char) != 1 ??"
+ r = sconf.CheckTypeSize('char', expect = 0)
+ assert r == 0, "sizeof(char) == 0 ??"
+ r = sconf.CheckTypeSize('char', expect = 2)
+ assert r == 0, "sizeof(char) == 2 ??"
+ r = sconf.CheckTypeSize('char')
+ assert r == 1, "sizeof(char) != 1 ??"
+ r = sconf.CheckTypeSize('const unsigned char')
+ assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+ # Checking C++
+ r = sconf.CheckTypeSize('const unsigned char', language = 'C++')
+ assert r == 1, "sizeof(const unsigned char) != 1 ??"
+
+ # Checking Non-existing type
+ r = sconf.CheckTypeSize('thistypedefhasnotchancetosexist_scons')
+ assert r == 0, \
+ "Checking size of thistypedefhasnotchancetosexist_scons succeeded ?"
+
+ finally:
+ sconf.Finish()
+
def test_(self):
"""Test SConf.CheckType()
"""
# Task control.
#
+
+_BuildFailures = []
+
+def GetBuildFailures():
+ return _BuildFailures
+
class BuildTask(SCons.Taskmaster.Task):
"""An SCons build task."""
progress = ProgressObject
display("scons: `%s' is up to date." % str(self.node))
def do_failed(self, status=2):
+ _BuildFailures.append(self.exception[1])
global exit_status
if self.options.ignore_errors:
SCons.Taskmaster.Task.executed(self)
class CleanTask(SCons.Taskmaster.Task):
"""An SCons clean task."""
- def dir_index(self, directory):
- dirname = lambda f, d=directory: os.path.join(d, f)
- files = map(dirname, os.listdir(directory))
-
- # os.listdir() isn't guaranteed to return files in any specific order,
- # but some of the test code expects sorted output.
- files.sort()
- return files
-
- def fs_delete(self, path, remove=1):
+ def fs_delete(self, path, pathstr, remove=1):
try:
if os.path.exists(path):
if os.path.isfile(path):
if remove: os.unlink(path)
- display("Removed " + path)
+ display("Removed " + pathstr)
elif os.path.isdir(path) and not os.path.islink(path):
# delete everything in the dir
- for p in self.dir_index(path):
+ entries = os.listdir(path)
+ # Sort for deterministic output (os.listdir() Can
+ # return entries in a random order).
+ entries.sort()
+ for e in entries:
+ p = os.path.join(path, e)
+ s = os.path.join(pathstr, e)
if os.path.isfile(p):
if remove: os.unlink(p)
- display("Removed " + p)
+ display("Removed " + s)
else:
- self.fs_delete(p, remove)
+ self.fs_delete(p, s, remove)
# then delete dir itself
if remove: os.rmdir(path)
- display("Removed directory " + path)
+ display("Removed directory " + pathstr)
except (IOError, OSError), e:
- print "scons: Could not remove '%s':" % str(path), e.strerror
+ print "scons: Could not remove '%s':" % pathstr, e.strerror
def show(self):
target = self.targets[0]
if SCons.Environment.CleanTargets.has_key(target):
files = SCons.Environment.CleanTargets[target]
for f in files:
- self.fs_delete(str(f), 0)
+ self.fs_delete(f.abspath, str(f), 0)
def remove(self):
target = self.targets[0]
if SCons.Environment.CleanTargets.has_key(target):
files = SCons.Environment.CleanTargets[target]
for f in files:
- self.fs_delete(str(f))
+ self.fs_delete(f.abspath, str(f))
execute = remove
module.__buildsys__)
def _main(parser):
+ import SCons
global exit_status
options = parser.values
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
+ if options.clean:
+ SCons.SConf.SetBuildType('clean')
+ if options.help:
+ SCons.SConf.SetBuildType('help')
SCons.SConf.SetCacheMode(options.config)
SCons.SConf.SetProgressDisplay(progress_display)
"""Leave the order of dependencies alone."""
return dependencies
- progress_display("scons: " + opening_message)
if options.taskmastertrace_file == '-':
tmtrace = sys.stdout
elif options.taskmastertrace_file:
global num_jobs
num_jobs = options.num_jobs
jobs = SCons.Job.Jobs(num_jobs, taskmaster)
- if num_jobs > 1 and jobs.num_jobs == 1:
- msg = "parallel builds are unsupported by this version of Python;\n" + \
- "\tignoring -j or num_jobs option.\n"
- SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
+ if num_jobs > 1:
+ msg = None
+ if jobs.num_jobs == 1:
+ msg = "parallel builds are unsupported by this version of Python;\n" + \
+ "\tignoring -j or num_jobs option.\n"
+ elif sys.platform == 'win32':
+ import SCons.Platform.win32
+ msg = SCons.Platform.win32.parallel_msg
+ if msg:
+ SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg)
memory_stats.append('before building targets:')
count_stats.append(('pre-', 'build'))
try:
+ progress_display("scons: " + opening_message)
jobs.run()
finally:
jobs.cleanup()
GetOption = Main.GetOption
SetOption = Main.SetOption
Progress = Main.Progress
+GetBuildFailures = Main.GetBuildFailures
#keep_going_on_error = Main.keep_going_on_error
#print_dtree = Main.print_dtree
'FindSourceFiles',
'Flatten',
'GetBuildPath',
+ 'Glob',
'Ignore',
'Install',
'InstallAs',
'ParseDepends',
'Precious',
'Repository',
+ 'Requires',
'SConsignFile',
'SideEffect',
'SourceCode',
node.set_state(SCons.Node.pending)
try:
- children = node.children()
+ children = node.children() + node.prerequisites
except SystemExit:
exc_value = sys.exc_info()[1]
e = SCons.Errors.ExplicitExit(node, exc_value.code)
self.scanned = 0
self.scanner = None
self.targets = [self]
+ self.prerequisites = []
class Builder:
def targets(self, node):
return node.targets
env['WINDOWSEXPSUFFIX'] = '${WIN32EXPSUFFIX}'
env['WINDOWSSHLIBMANIFESTPREFIX'] = ''
- env['WINDOWSSHLIBMANIFESTSUFFIX'] = env['SHLIBSUFFIX'] + '.manifest'
+ env['WINDOWSSHLIBMANIFESTSUFFIX'] = '${SHLIBSUFFIX}.manifest'
env['WINDOWSPROGMANIFESTPREFIX'] = ''
- env['WINDOWSPROGMANIFESTSUFFIX'] = env['PROGSUFFIX'] + '.manifest'
+ env['WINDOWSPROGMANIFESTSUFFIX'] = '${PROGSUFFIX}.manifest'
env['REGSVRACTION'] = regServerCheck
env['REGSVR'] = os.path.join(SCons.Platform.win32.get_system_root(),'System32','regsvr32')
def Package(env, target=None, source=None, **kw):
""" Entry point for the package tool.
"""
- # first check some arguments
+ # check if we need to find the source files ourself
if not source:
source = env.FindInstalledFiles()
if len(source)==0:
raise UserError, "No source for Package() given"
- # has the option for this Tool been set?
+ # decide which types of packages shall be built. Can be defined through
+ # four mechanisms: command line argument, keyword argument,
+ # environment argument and default selection( zip or tar.gz ) in that
+ # order.
try: kw['PACKAGETYPE']=env['PACKAGETYPE']
except KeyError: pass
kw['PACKAGETYPE']='zip'
else:
raise UserError, "No type for Package() given"
+
PACKAGETYPE=kw['PACKAGETYPE']
if not is_List(PACKAGETYPE):
- #PACKAGETYPE=PACKAGETYPE.split(',')
PACKAGETYPE=string.split(PACKAGETYPE, ',')
- # now load the needed packagers.
+ # load the needed packagers.
def load_packager(type):
try:
file,path,desc=imp.find_module(type, __path__)
packagers=map(load_packager, PACKAGETYPE)
- # now try to setup the default_target and the default PACKAGEROOT
- # arguments.
+ # set up targets and the PACKAGEROOT
try:
# fill up the target list with a default target name until the PACKAGETYPE
# list is of the same size as the target list.
- if target==None or target==[]:
- target=["%(NAME)s-%(VERSION)s"%kw]
+ if not target: target = []
+
+ size_diff = len(PACKAGETYPE)-len(target)
+ default_name = "%(NAME)s-%(VERSION)s"
- size_diff=len(PACKAGETYPE)-len(target)
if size_diff>0:
- target.extend([target]*size_diff)
+ default_target = default_name%kw
+ target.extend( [default_target]*size_diff )
if not kw.has_key('PACKAGEROOT'):
- kw['PACKAGEROOT']="%(NAME)s-%(VERSION)s"%kw
+ kw['PACKAGEROOT'] = default_name%kw
except KeyError, e:
- raise SCons.Errors.UserError( "Missing PackageTag '%s'"%e.args[0] )
+ raise SCons.Errors.UserError( "Missing Packagetag '%s'"%e.args[0] )
# setup the source files
source=env.arg2nodes(source, env.fs.Entry)
targets=[]
try:
for packager in packagers:
- t=apply(packager.package, [env,target,source], kw)
+ t=[target.pop(0)]
+ t=apply(packager.package, [env,t,source], kw)
targets.extend(t)
+ assert( len(target) == 0 )
+
except KeyError, e:
- raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+ raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\
% (e.args[0],packager.__name__) )
except TypeError, e:
# this exception means that a needed argument for the packager is
if len(args)==0:
raise # must be a different error, so reraise
elif len(args)==1:
- raise SCons.Errors.UserError( "Missing PackageTag '%s' for %s packager"\
+ raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\
% (args[0],packager.__name__) )
else:
- raise SCons.Errors.UserError( "Missing PackageTags '%s' for %s packager"\
+ raise SCons.Errors.UserError( "Missing Packagetags '%s' for %s packager"\
% (", ".join(args),packager.__name__) )
target=env.arg2nodes(target, env.fs.Entry)
+class UniqueList(UserList):
+ def __init__(self, seq = []):
+ UserList.__init__(self, seq)
+ self.unique = True
+ def __make_unique(self):
+ if not self.unique:
+ self.data = uniquer_hashables(self.data)
+ self.unique = True
+ def __lt__(self, other):
+ self.__make_unique()
+ return UserList.__lt__(self, other)
+ def __le__(self, other):
+ self.__make_unique()
+ return UserList.__le__(self, other)
+ def __eq__(self, other):
+ self.__make_unique()
+ return UserList.__eq__(self, other)
+ def __ne__(self, other):
+ self.__make_unique()
+ return UserList.__ne__(self, other)
+ def __gt__(self, other):
+ self.__make_unique()
+ return UserList.__gt__(self, other)
+ def __ge__(self, other):
+ self.__make_unique()
+ return UserList.__ge__(self, other)
+ def __cmp__(self, other):
+ self.__make_unique()
+ return UserList.__cmp__(self, other)
+ def __len__(self):
+ self.__make_unique()
+ return UserList.__len__(self)
+ def __getitem__(self, i):
+ self.__make_unique()
+ return UserList.__getitem__(self, i)
+ def __setitem__(self, i, item):
+ UserList.__setitem__(self, i, item)
+ self.unique = False
+ def __getslice__(self, i, j):
+ self.__make_unique()
+ return UserList.__getslice__(self, i, j)
+ def __setslice__(self, i, j, other):
+ UserList.__setslice__(self, i, j, other)
+ self.unique = False
+ def __add__(self, other):
+ result = UserList.__add__(self, other)
+ result.unique = False
+ return result
+ def __radd__(self, other):
+ result = UserList.__radd__(self, other)
+ result.unique = False
+ return result
+ def __iadd__(self, other):
+ result = UserList.__iadd__(self, other)
+ result.unique = False
+ return result
+ def __mul__(self, other):
+ result = UserList.__mul__(self, other)
+ result.unique = False
+ return result
+ def __rmul__(self, other):
+ result = UserList.__rmul__(self, other)
+ result.unique = False
+ return result
+ def __imul__(self, other):
+ result = UserList.__imul__(self, other)
+ result.unique = False
+ return result
+ def append(self, item):
+ UserList.append(self, item)
+ self.unique = False
+ def insert(self, i):
+ UserList.insert(self, i)
+ self.unique = False
+ def count(self, item):
+ self.__make_unique()
+ return UserList.count(self, item)
+ def index(self, item):
+ self.__make_unique()
+ return UserList.index(self, item)
+ def reverse(self):
+ self.__make_unique()
+ UserList.reverse(self)
+ def sort(self, *args, **kwds):
+ self.__make_unique()
+ #return UserList.sort(self, *args, **kwds)
+ return apply(UserList.sort, (self,)+args, kwds)
+ def extend(self, other):
+ UserList.extend(self, other)
+ self.unique = False
+
+
+
class Unbuffered:
"""
A proxy class that wraps a file object, flushing after every write,
import sets
__builtin__.set = sets.Set
+import fnmatch
+try:
+ fnmatch.filter
+except AttributeError:
+ # Pre-2.2 Python has no fnmatch.filter() function.
+ def filter(names, pat):
+ """Return the subset of the list NAMES that match PAT"""
+ import os,posixpath
+ result=[]
+ pat = os.path.normcase(pat)
+ if not fnmatch._cache.has_key(pat):
+ import re
+ res = fnmatch.translate(pat)
+ fnmatch._cache[pat] = re.compile(res)
+ match = fnmatch._cache[pat].match
+ if os.path is posixpath:
+ # normcase on posix is NOP. Optimize it away from the loop.
+ for name in names:
+ if match(name):
+ result.append(name)
+ else:
+ for name in names:
+ if match(os.path.normcase(name)):
+ result.append(name)
+ return result
+ fnmatch.filter = filter
+ del filter
+
+
# If we need the compatibility version of textwrap, it must be imported
# before optparse, which uses it.
try:
#include <stdio.h>
#include <stdlib.h>
+extern void
+library_function(void);
+
int
main(int argc, char *argv[])
{
test.write('main.c', r"""
#include <stdlib.h>
+extern void
+library_function();
+
int
main(int argc, char *argv[])
{
--- /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 Clean() files in a BuildDir() that's underneath us.
+(At one point this didn't work because we were using str() instead of
+abspath to remove the files, which would interfere with the removal by
+returning a path relative to the BuildDir(), not the top-level SConstruct
+directory, if the source directory was the top-level directory.)
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+BuildDir('build0', '.', duplicate=0)
+BuildDir('build1', '.', duplicate=1)
+
+def build_sample(target, source, env):
+ targetdir = str(target[0].dir)
+ target = str(target[0])
+ open(target, 'wb').write(open(str(source[0]), 'rb').read())
+ open(targetdir+'/sample.junk', 'wb').write('Side effect!\\n')
+
+t0 = Command("build0/sample.out", "sample.in", build_sample)
+t1 = Command("build1/sample.out", "sample.in", build_sample)
+
+Clean(t0, 'build0/sample.junk')
+Clean(t1, 'build1/sample.junk')
+""")
+
+test.write('sample.in', "sample.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['build0', 'sample.out'], "sample.in\n")
+test.must_exist(['build0', 'sample.junk'])
+
+test.must_match(['build1', 'sample.out'], "sample.in\n")
+test.must_exist(['build1', 'sample.junk'])
+
+test.run(arguments = '-c .')
+
+test.must_not_exist(['build', 'sample.out'])
+test.must_not_exist(['build', 'sample.junk'])
+
+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 explicit use of File() Nodes in a BuildDir, followed by
+*direct* creation of the file by Python in the SConscript file itself,
+works correctly, with both duplicate=0 and duplicate=1.
+
+Right now it only works if you explicitly str() the Node before the file
+is created on disk, but we at least want to make sure that continues
+to work. The non-str() case, which doesn't currently work, is captured
+here but commented out.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+SConscript('src/SConscript', build_dir='build0', chdir=1, duplicate=0)
+SConscript('src/SConscript', build_dir='build1', chdir=1, duplicate=1)
+""")
+
+test.write(['src', 'SConscript'], """\
+#f1_in = File('f1.in')
+#Command('f1.out', f1_in, Copy('$TARGET', '$SOURCE'))
+#open('f1.in', 'wb').write("f1.in\\n")
+
+f2_in = File('f2.in')
+str(f2_in)
+Command('f2.out', f2_in, Copy('$TARGET', '$SOURCE'))
+open('f2.in', 'wb').write("f2.in\\n")
+""")
+
+test.run(arguments = '--tree=all .')
+
+#test.must_match(['build0', 'f1.out'], "f1.in\n")
+test.must_match(['build0', 'f2.out'], "f2.in\n")
+
+#test.must_match(['build1', 'f1.out'], "f1.in\n")
+test.must_match(['build1', 'f2.out'], "f2.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()
#include <stdio.h>
#include <stdlib.h>
+extern void
+bar(void);
+
int
main(int argc, char *argv[]) {
bar();
test.write('SConstruct', """
env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCE',
SHCCCOMSTR = 'Building $TARGET from $SOURCE',
+ SHOBJPREFIX='',
SHOBJSUFFIX='.obj')
env.SharedObject(target = 'test1', source = 'test1.c')
env.SharedObject(target = 'test2', source = 'test2%(alt_c_suffix)s')
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
printf("foo.cxx\n");
exit (0);
}
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
printf("foo.cxx\n");
exit (0);
}
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
printf("foo.c\n");
exit (0);
}
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
printf("foo.c\n");
exit (0);
}
test.write('SConstruct', """
env = Environment(SHCXXCOM = r'%(_python_)s mycc.py $TARGET $SOURCE',
SHCXXCOMSTR = 'Building shared object $TARGET from $SOURCE',
- SHOBJSUFFIX='.obj')
+ SHOBJPREFIX='', SHOBJSUFFIX='.obj')
env.SharedObject(target = 'test1', source = 'test1.cpp')
env.SharedObject(target = 'test2', source = 'test2.cc')
env.SharedObject(target = 'test3', source = 'test3.cxx')
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
-Verify that CacheDir() works even when using timestamp signatures.
+Verify that CacheDir() works when using SourceSignatures('timestamp')
+and TargetSignatures 'content'.
"""
import TestSCons
test.write('file.in', "file.in\n")
-test.run()
+test.run(arguments = '.')
test.must_match('file.out', "file.in\n")
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', 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 that CAcheDir() works when using 'timestamp-match' decisions.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+Decider('timestamp-match')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', 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 that CAcheDir() works when using 'timestamp-newer' decisions.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+Decider('timestamp-newer')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', 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 that CacheDir() works when using both SourceSignatures()
+and TargetSignatures values of 'timestamp'.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(['SConstruct'], """\
+SourceSignatures('timestamp')
+TargetSignatures('timestamp')
+CacheDir('cache')
+Command('file.out', 'file.in', Copy('$TARGET', '$SOURCE'))
+""")
+
+test.write('file.in', "file.in\n")
+
+test.run(arguments = '--cache-show --debug=explain .')
+
+test.must_match('file.out', "file.in\n")
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.sleep()
+
+test.touch('file.in')
+
+test.not_up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.up_to_date(options = '--cache-show --debug=explain', arguments = '.')
+
+test.pass_test()
# oscillate between those values.
test.write('SConstruct', """
Execute(Chmod('f1', 0666))
+Execute(Chmod(('f1-File'), 0666))
Execute(Chmod('d2', 0777))
+Execute(Chmod(Dir('d2-Dir'), 0777))
def cat(env, source, target):
target = str(target[0])
source = map(str, source)
""")
test.write('f1', "f1\n")
+test.write('f1-File', "f1-File\n")
test.subdir('d2')
test.write(['d2', 'file'], "d2/file\n")
+test.subdir('d2-Dir')
+test.write(['d2-Dir', 'file'], "d2-Dir/file\n")
test.write('bar.in', "bar.in\n")
test.write('f3', "f3\n")
test.subdir('d4')
test.write('f7.out-Chmod', "f7.out-Chmod\n")
os.chmod(test.workpath('f1'), 0444)
+os.chmod(test.workpath('f1-File'), 0444)
os.chmod(test.workpath('d2'), 0555)
+os.chmod(test.workpath('d2-Dir'), 0555)
os.chmod(test.workpath('f3'), 0444)
os.chmod(test.workpath('d4'), 0555)
os.chmod(test.workpath('f5'), 0444)
os.chmod(test.workpath('Chmod-f7.in'), 0444)
os.chmod(test.workpath('f7.out-Chmod'), 0444)
-expect = test.wrap_stdout(read_str = 'Chmod("f1", 0666)\nChmod("d2", 0777)\n',
+expect = test.wrap_stdout(read_str = """\
+Chmod("f1", 0666)
+Chmod("f1-File", 0666)
+Chmod("d2", 0777)
+Chmod("d2-Dir", 0777)
+""",
build_str = """\
cat(["bar.out"], ["bar.in"])
Chmod("f3", 0666)
s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE])
test.fail_test(s != 0444)
+s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE])
+test.fail_test(s != 0444)
s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE])
test.fail_test(s != 0555)
+s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE])
+test.fail_test(s != 0555)
test.must_not_exist('bar.out')
s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE])
test.fail_test(s != 0444)
s = stat.S_IMODE(os.stat(test.workpath('f1'))[stat.ST_MODE])
test.fail_test(s != 0666)
+s = stat.S_IMODE(os.stat(test.workpath('f1-File'))[stat.ST_MODE])
+test.fail_test(s != 0666)
s = stat.S_IMODE(os.stat(test.workpath('d2'))[stat.ST_MODE])
test.fail_test(s != 0777)
+s = stat.S_IMODE(os.stat(test.workpath('d2-Dir'))[stat.ST_MODE])
+test.fail_test(s != 0777)
test.must_match('bar.out', "bar.in\n")
s = stat.S_IMODE(os.stat(test.workpath('f3'))[stat.ST_MODE])
test.fail_test(s != 0666)
env = Environment()
import os
env.AppendENVPath('PATH', os.environ['PATH'])
-conf = Configure(env)
+conf = Configure(env, clean=int(ARGUMENTS['clean']))
r1 = conf.CheckCHeader( 'math.h' )
r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
env = conf.Finish()
"Checking for C header file no_std_c_header.h... "
]
-unexpected = []
+test.run(arguments = '-c clean=0')
+test.must_not_contain_lines(lines, test.stdout())
-test.run(arguments = '-c')
+test.run(arguments = '-c clean=1')
+test.must_contain_lines(lines, test.stdout())
-for line in lines:
- if string.find(test.stdout(), line) != -1:
- unexpected.append(line)
+test.run(arguments = '--clean clean=0')
+test.must_not_contain_lines(lines, test.stdout())
-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.run(arguments = '--clean clean=1')
+test.must_contain_lines(lines, test.stdout())
test.pass_test()
env = Environment()
import os
env.AppendENVPath('PATH', os.environ['PATH'])
-conf = Configure(env)
+conf = Configure(env, help=int(ARGUMENTS['help']))
r1 = conf.CheckCHeader( 'math.h' )
r2 = conf.CheckCHeader( 'no_std_c_header.h' ) # leads to compile error
env = conf.Finish()
"Checking for C header file no_std_c_header.h... "
]
-unexpected = []
+# The help setting should have no effect on -H, so the -H output
+# should never contain the lines.
+test.run(arguments = '-H help=0')
+test.must_not_contain_lines(lines, test.stdout())
-test.run(arguments = '-H')
+test.run(arguments = '-H help=1')
+test.must_not_contain_lines(lines, test.stdout())
-for line in lines:
- if string.find(test.stdout(), line) != -1:
- unexpected.append(line)
+# For -h and --help, the lines appear or not depending on how Configure()
+# is initialized.
+test.run(arguments = '-h help=0')
+test.must_not_contain_lines(lines, test.stdout())
-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 help=1')
+test.must_contain_lines(lines, test.stdout())
-test.run(arguments = '-h')
+test.run(arguments = '--help help=0')
+test.must_not_contain_lines(lines, test.stdout())
-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.run(arguments = '--help help=1')
+test.must_contain_lines(lines, test.stdout())
test.pass_test()
test.write('SConstruct', """
Execute(Copy('f1.out', 'f1.in'))
-Execute(Copy('d2.out', 'd2.in'))
-Execute(Copy('d3.out', 'f3.in'))
+Execute(Copy(File('d2.out'), 'd2.in'))
+Execute(Copy('d3.out', File('f3.in')))
def cat(env, source, target):
target = str(target[0])
source = map(str, source)
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
"""
-Verify that we print a useful message (and exit non-zero) if an external
-error occurs while deciding if a Node is current or not.
+A currently disabled test that used to verify that we print a useful
+message (and exit non-zero) if an external error occurs while deciding
+if a Node is current or not.
+
+This behavior changed when the Big Signature Refactoring changed when
+signature calculation happens to *after* a Node has been visited (and
+therefore visiting source Nodes in turn). Creating an analogous situation
+in the new code isn't obvious, and It's not clear whether we need it
+anyway, so we're going to leave this checked in but disabled for now.
"""
import sys
test = TestSCons.TestSCons()
+test.skip_test('Test not useful with current code; skipping.\n')
+
work_file_out = test.workpath('work', 'file.out')
test.subdir('install', 'work')
SHF77COMSTR = 'Building f77 $TARGET from $SOURCES',
SHF77PPCOM = r'%(_python_)s myfc.py f77pp $TARGET $SOURCES',
SHF77PPCOMSTR = 'Building f77pp $TARGET from $SOURCES',
- SHOBJSUFFIX='.shobj')
+ SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
env.SharedObject(source = 'test01.f')
env.SharedObject(source = 'test02.F')
env.SharedObject(source = 'test03.for')
SHF90COMSTR = 'Building f90 $TARGET from $SOURCES',
SHF90PPCOM = r'%(_python_)s myfc.py f90pp $TARGET $SOURCES',
SHF90PPCOMSTR = 'Building f90pp $TARGET from $SOURCES',
- SHOBJSUFFIX='.shobj')
+ SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
env.SharedObject(source = 'test01.f90')
env.SharedObject(source = 'test02.F90')
""" % locals())
SHF95COMSTR = 'Building f95 $TARGET from $SOURCES',
SHF95PPCOM = r'%(_python_)s myfc.py f95pp $TARGET $SOURCES',
SHF95PPCOMSTR = 'Building f95pp $TARGET from $SOURCES',
- SHOBJSUFFIX='.shobj')
+ SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
env.SharedObject(source = 'test01.f95')
env.SharedObject(source = 'test02.F95')
""" % locals())
SHFORTRANCOMSTR = 'Building fortran $TARGET from $SOURCES',
SHFORTRANPPCOM = r'%(_python_)s myfc.py fortranpp $TARGET $SOURCES',
SHFORTRANPPCOMSTR = 'Building fortranpp $TARGET from $SOURCES',
- SHOBJSUFFIX='.shobj')
+ SHOBJPREFIX='', SHOBJSUFFIX='.shobj')
env.SharedObject(source = 'test01.f')
env.SharedObject(source = 'test02.F')
env.SharedObject(source = 'test03.for')
--- /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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+ import threading
+except ImportError:
+ # if threads are not supported, then
+ # there is nothing to test
+ TestCmd.no_result()
+ sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+contents = r"""\
+import sys
+if sys.argv[0] == 'mypass.py':
+ open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+ exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+ exit_value = 1
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE')
+
+def print_build_failures():
+ from SCons.Script import GetBuildFailures
+ bf_list = GetBuildFailures()
+ bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+ for bf in bf_list:
+ print "%%s failed: %%s" %% (bf.node, bf.errstr)
+
+try:
+ import atexit
+except ImportError:
+ import sys
+ sys.exitfunc = print_build_failures
+else:
+ atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: done building targets (errors occurred during build).
+f4 failed: Error 1
+f5 failed: Error 1
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+scons: *** [f5] Error 1
+"""
+
+test.run(arguments = '-k .',
+ status = 2,
+ stdout = expect_stdout,
+ stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_match(test.workpath('f6'), 'f6.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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+ import threading
+except ImportError:
+ # if threads are not supported, then
+ # there is nothing to test
+ TestCmd.no_result()
+ sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+# We want to verify that -j 4 starts all four jobs, the first and last of
+# which fail and the second and third of which succeed, and then stops
+# processing due to the build failures. To try to control the timing,
+# the created build scripts use marker directories to avoid doing their
+# processing until the previous script has finished.
+
+contents = r"""\
+import os.path
+import sys
+import time
+wait_marker = sys.argv[1] + '.marker'
+write_marker = sys.argv[2] + '.marker'
+if wait_marker != '-.marker':
+ while not os.path.exists(wait_marker):
+ time.sleep(1)
+if sys.argv[0] == 'mypass.py':
+ open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+ exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+ exit_value = 1
+if write_marker != '-.marker':
+ os.mkdir(write_marker)
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE')
+
+def print_build_failures():
+ from SCons.Script import GetBuildFailures
+ bf_list = GetBuildFailures()
+ bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+ for bf in bf_list:
+ print "%%s failed: %%s" %% (bf.node, bf.errstr)
+
+try:
+ import atexit
+except ImportError:
+ import sys
+ sys.exitfunc = print_build_failures
+else:
+ atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: building terminated because of errors.
+f4 failed: Error 1
+f5 failed: Error 1
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+scons: *** [f5] Error 1
+"""
+
+test.run(arguments = '-j 4 .',
+ status = 2,
+ stdout = expect_stdout,
+ stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_match(test.workpath('f6'), 'f6.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.
+#
+
+"""
+Verify that the GetBuildFailures() function returns a list of
+BuildError exceptions. Also verify printing the BuildError
+attributes we expect to be most commonly used.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+ import threading
+except ImportError:
+ # if threads are not supported, then
+ # there is nothing to test
+ TestCmd.no_result()
+ sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+contents = r"""\
+import sys
+if sys.argv[0] == 'mypass.py':
+ open(sys.argv[3], 'wb').write(open(sys.argv[4], 'rb').read())
+ exit_value = 0
+elif sys.argv[0] == 'myfail.py':
+ exit_value = 1
+sys.exit(exit_value)
+"""
+
+test.write('mypass.py', contents)
+test.write('myfail.py', contents)
+
+test.write('SConstruct', """\
+Command('f3', 'f3.in', r'@%(_python_)s mypass.py - f3 $TARGET $SOURCE')
+Command('f4', 'f4.in', r'@%(_python_)s myfail.py f3 f4 $TARGET $SOURCE')
+Command('f5', 'f5.in', r'@%(_python_)s myfail.py f4 f5 $TARGET $SOURCE')
+Command('f6', 'f6.in', r'@%(_python_)s mypass.py f5 - $TARGET $SOURCE')
+
+def print_build_failures():
+ from SCons.Script import GetBuildFailures
+ import string
+ bf_list = GetBuildFailures()
+ bf_list.sort(lambda a,b: cmp(a.filename, b.filename))
+ for bf in bf_list:
+ print "%%s failed (%%s): %%s" %% (bf.node, bf.status, bf.errstr)
+ print " %%s" %% string.join(bf.command)
+
+try:
+ import atexit
+except ImportError:
+ import sys
+ sys.exitfunc = print_build_failures
+else:
+ atexit.register(print_build_failures)
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+expect_stdout = """\
+scons: Reading SConscript files ...
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: building terminated because of errors.
+f4 failed (1): Error 1
+ %(_python_)s myfail.py f3 f4 "f4" "f4.in"
+""" % locals()
+
+expect_stderr = """\
+scons: *** [f4] Error 1
+"""
+
+test.run(arguments = '.',
+ status = 2,
+ stdout = expect_stdout,
+ stderr = expect_stderr)
+
+test.must_match(test.workpath('f3'), 'f3.in\n')
+test.must_not_exist(test.workpath('f4'))
+test.must_not_exist(test.workpath('f5'))
+test.must_not_exist(test.workpath('f6'))
+
+
+
+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 default use of the Glob() function within a BuildDir()
+finds the local file Nodes.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+BuildDir('var1', 'src')
+BuildDir('var2', 'src')
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['src', 'SConscript'], """\
+env = Environment()
+
+def concatenate(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+test.write(['src', 'f3.in'], "src/f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.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 the Glob() function finds files in repositories.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('work',
+ 'repository',
+ ['repository', 'src'],
+ ['repository', 'src', 'sub1'],
+ ['repository', 'src', 'sub2'])
+
+work_aaa = test.workpath('work', 'aaa')
+work_bbb = test.workpath('work', 'bbb')
+work_ccc = test.workpath('work', 'ccc')
+work_src_xxx = test.workpath('work', 'src', 'xxx')
+work_src_yyy = test.workpath('work', 'src', 'yyy')
+
+opts = "-Y " + test.workpath('repository')
+
+test.write(['repository', 'SConstruct'], """\
+def cat(env, source, target):
+ target = str(target[0])
+ source = map(str, source)
+ f = open(target, "wb")
+ for src in source:
+ f.write(open(src, "rb").read())
+ f.close()
+
+env = Environment(BUILDERS={'Build':Builder(action=cat)})
+env.Build('aaa.out', Glob('a*.in'))
+env.Build('bbb.out', Glob('b*.in'))
+env.Build('ccc.out', Glob('c*.in'))
+SConscript('src/SConscript', "env")
+""")
+
+test.write(['repository', 'aaa.in'], "repository/aaa.in\n")
+test.write(['repository', 'bbb.in'], "repository/bbb.in\n")
+test.write(['repository', 'ccc.in'], "repository/ccc.in\n")
+
+test.write(['repository', 'src', 'SConscript'], """
+Import("env")
+env.Build('xxx.out', Glob('x*.in'))
+env.Build('yyy.out', Glob('yy?.in'))
+zzz_in = Glob('*/zzz.in')
+zzz_in.sort(lambda a,b: cmp(a.abspath, b.abspath))
+env.Build('zzz.out', zzz_in)
+""")
+
+test.write(['repository', 'src', 'xxx.in'], "repository/src/xxx.in\n")
+test.write(['repository', 'src', 'yyy.in'], "repository/src/yyy.in\n")
+test.write(['repository', 'src', 'sub1', 'zzz.in'], "repository/src/sub1/zzz.in\n")
+test.write(['repository', 'src', 'sub2', 'zzz.in'], "repository/src/sub2/zzz.in\n")
+
+#
+# Make the repository non-writable,
+# so we'll detect if we try to write into it accidentally.
+test.writable('repository', 0)
+
+#
+test.run(chdir = 'work', options = opts, arguments = 'aaa.out')
+
+test.must_match(['work', 'aaa.out'], "repository/aaa.in\n")
+test.must_not_exist(test.workpath('work', 'bbb.out'))
+test.must_not_exist(test.workpath('work', 'ccc.out'))
+test.must_not_exist(test.workpath('work', 'src', 'xxx.out'))
+test.must_not_exist(test.workpath('work', 'src', 'yyy.out'))
+
+test.run(chdir = 'work', options = opts, arguments = 'bbb.out src')
+
+test.must_match(['work', 'bbb.out'], "repository/bbb.in\n")
+test.must_not_exist(test.workpath('work', 'ccc.out'))
+test.must_match(['work', 'src', 'xxx.out'], "repository/src/xxx.in\n")
+test.must_match(['work', 'src', 'yyy.out'], "repository/src/yyy.in\n")
+
+expect = """\
+repository/src/sub1/zzz.in
+repository/src/sub2/zzz.in
+"""
+
+test.must_match(['work', 'src', 'zzz.out'], expect)
+
+#
+test.run(chdir = 'work', options = opts, arguments = '.')
+
+test.must_match(['work', 'ccc.out'], "repository/ccc.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 basic operation of the Glob() function.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment()
+
+def concatenate(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "f1.in\nf2.in\nf3.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 use of the Glob() function within a BuildDir() returns the
+file Nodes in the source directory when the source= keyword argument is
+specified (and duplicate=0 is specified for the BuildDir()).
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src', 'var1', 'var2')
+
+test.write('SConstruct', """\
+env = Environment()
+
+def concatenate(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+Export("env")
+
+BuildDir('var1', 'src', duplicate=0)
+BuildDir('var2', 'src', duplicate=0)
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['var1', 'SConscript'], """\
+Import("env")
+
+f_in = Glob('f[45].in', source=True)
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['var2', 'SConscript'], """\
+Import("env")
+
+f_in = Glob('f[67].in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+
+test.write(['src', 'f4.in'], "src/f4.in\n")
+test.write(['src', 'f5.in'], "src/f5.in\n")
+test.write(['src', 'f6.in'], "src/f6.in\n")
+test.write(['src', 'f7.in'], "src/f7.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f4.in\nsrc/f5.in\n")
+test.must_match(['var2', 'f.out'], "src/f6.in\nsrc/f7.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 use of the Glob() function with the strings= option works
+when they're used in the same SConscript file (and therefore the same
+directory) as input to a Builder call.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('src')
+
+test.write('SConstruct', """\
+BuildDir('var1', 'src')
+BuildDir('var2', 'src')
+
+SConscript('var1/SConscript')
+SConscript('var2/SConscript')
+""")
+
+test.write(['src', 'SConscript'], """\
+env = Environment()
+
+def concatenate(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('f*.in', strings=True)
+f_in.sort()
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['src', 'f1.in'], "src/f1.in\n")
+test.write(['src', 'f2.in'], "src/f2.in\n")
+test.write(['src', 'f3.in'], "src/f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['var1', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.in\n")
+test.must_match(['var2', 'f.out'], "src/f1.in\nsrc/f2.in\nsrc/f3.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 Glob() works to find Nodes underneath an explicitly-
+named subdirectory.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('subdir')
+
+test.write('SConstruct', """\
+env = Environment()
+
+def concatenate(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Concatenate'] = Builder(action=concatenate)
+
+f_in = Glob('subdir/*.in')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Concatenate('f.out', f_in)
+""")
+
+test.write(['subdir', 'file.in'], "subdir/file.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "subdir/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 the ability to Glob() using a pattern from a construction variable
+expansion.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+env = Environment(PATTERN = 'f*.in')
+
+def copy(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+
+env['BUILDERS']['Copy'] = Builder(action=copy)
+
+f_in = env.Glob('$PATTERN')
+f_in.sort(lambda a,b: cmp(a.name, b.name))
+env.Copy('f.out', f_in)
+""")
+
+test.write('f1.in', "f1.in\n")
+test.write('f2.in', "f2.in\n")
+test.write('f3.in', "f3.in\n")
+
+test.run(arguments = '.')
+
+test.must_match('f.out', "f1.in\nf2.in\nf3.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 can Install() and InstallAs() from a construction
+environment cloned from a clone.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env1 = Environment(DESTDIR='sub1', tools=[])
+
+# Call env1.Install() but not env1.InstallAs() *before* we clone it.
+# This is to verify that re-initializing the Install() attribute on the
+# construction environment doesn't mess up the environment settings in
+# a way that leaves the InstallAs() intializer in place, which leads to
+# infinite recursion.
+env1.Install('$DESTDIR', 'foo.in')
+
+env2 = env1.Clone(DESTDIR='sub2')
+env3 = env2.Clone(DESTDIR='sub3')
+
+env2.Install('$DESTDIR', 'foo.in')
+env3.Install('$DESTDIR', 'foo.in')
+
+env1.InstallAs('$DESTDIR/foo.out', 'foo.in')
+env2.InstallAs('$DESTDIR/foo.out', 'foo.in')
+env3.InstallAs('$DESTDIR/foo.out', 'foo.in')
+""")
+
+test.write('foo.in', "foo.in\n")
+
+test.run(arguments = '.')
+
+test.must_match(['sub1', 'foo.in'], "foo.in\n")
+test.must_match(['sub2', 'foo.in'], "foo.in\n")
+test.must_match(['sub3', 'foo.in'], "foo.in\n")
+
+test.must_match(['sub1', 'foo.out'], "foo.in\n")
+test.must_match(['sub2', 'foo.out'], "foo.in\n")
+test.must_match(['sub3', 'foo.out'], "foo.in\n")
+
+test.pass_test()
env = Environment(SHCCCOM = r'%(_python_)s mycc.py $TARGET $SOURCES',
SHLINKCOM = r'%(_python_)s mylink.py $TARGET $SOURCES',
SHLINKCOMSTR = 'Linking shared $TARGET from $SOURCES',
+ SHOBJPREFIX = '',
SHOBJSUFFIX = '.obj',
SHLIBPREFIX = '',
SHLIBSUFFIX = '.dll')
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
f1();
f2a();
f2b();
test.skip_test(msg)\r
\r
test.write('SConstruct', """\\r
-env = Environment(PDB = '${TARGET}.pdb')\r
+env = Environment(PDB = '${TARGET.base}.pdb')\r
env.Program('test1.cpp')\r
env.Program('test2.cpp')\r
""")\r
\r
test.run(arguments = '.')\r
\r
-test.must_exist('test1%s' % _exe)\r
-test.must_exist('test1%s.pdb' % _exe)\r
-test.must_exist('test2%s' % _exe)\r
-test.must_exist('test2%s.pdb' % _exe)\r
+test.must_exist('test1%s' % _exe)\r
+test.must_exist('test1.pdb')\r
+test.must_exist('test2%s' % _exe)\r
+test.must_exist('test2.pdb')\r
\r
test.pass_test()\r
test.write(['work1', 'SConstruct'], """
Execute(Mkdir('d1'))
+Execute(Mkdir(Dir('#d1-Dir')))
def cat(env, source, target):
target = str(target[0])
source = map(str, source)
test.write(['work1', 'f5.in'], "f5.in\n")
test.write(['work1', 'f6.in'], "f6.in\n")
-expect = test.wrap_stdout(read_str = 'Mkdir("d1")\n',
+expect = test.wrap_stdout(read_str = 'Mkdir("d1")\nMkdir("d1-Dir")\n',
build_str = """\
cat(["f2.out"], ["f2.in"])
Mkdir("d3")
test.run(chdir = 'work1', options = '-n', arguments = '.', stdout = expect)
test.must_not_exist(['work1', 'd1'])
+test.must_not_exist(['work1', 'd1-Dir'])
test.must_not_exist(['work1', 'f2.out'])
test.must_not_exist(['work1', 'd3'])
test.must_not_exist(['work1', 'd4'])
test.run(chdir = 'work1')
test.must_exist(['work1', 'd1'])
+test.must_exist(['work1', 'd1-Dir'])
test.must_match(['work1', 'f2.out'], "f2.in\n")
test.must_exist(['work1', 'd3'])
test.must_exist(['work1', 'd4'])
test.write('SConstruct', """
Execute(Move('f1.out', 'f1.in'))
+Execute(Move('File-f1.out', File('f1.in-File')))
def cat(env, source, target):
target = str(target[0])
source = map(str, source)
""")
test.write('f1.in', "f1.in\n")
+test.write('f1.in-File', "f1.in-File\n")
test.write('f2.in', "f2.in\n")
test.write('f3.in', "f3.in\n")
test.write('f4.in', "f4.in\n")
test.write('f6.in', "f6.in\n")
test.write('f6.in-Move', "f6.in-Move\n")
-expect = test.wrap_stdout(read_str = 'Move("f1.out", "f1.in")\n',
+expect = test.wrap_stdout(read_str = """\
+Move("f1.out", "f1.in")
+Move("File-f1.out", "f1.in-File")
+""",
build_str = """\
cat(["f2.out"], ["f2.in"])
Move("f3.out", "f3.in")
test.run(options = '-n', arguments = '.', stdout = expect)
test.must_not_exist('f1.out')
+test.must_not_exist('File-f1.out')
test.must_not_exist('f2.out')
test.must_not_exist('f3.out')
test.must_not_exist('f4.out')
test.run()
test.must_match('f1.out', "f1.in\n")
+test.must_match('File-f1.out', "f1.in-File\n")
test.must_match('f2.out', "f2.in\n")
test.must_not_exist('f3.in')
test.must_match('f3.out', "f3.in\n")
test.subdir('bld', 'src', ['src', 'subsrcdir'])
sconstruct = r"""
-foo = Environment(SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1)
-bar = Environment(SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1)
+foo = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(fooflags)s', WINDOWS_INSERT_DEF=1)
+bar = Environment(SHOBJPREFIX='', SHCXXFLAGS = '%(barflags)s', WINDOWS_INSERT_DEF=1)
src = Dir('src')
BuildDir('bld', src, duplicate=1)
Nodes=[]
int
main(int argc, char *argv[])
{
- argv[argc++] = "--";
+ argv[argc++] = (char *)"--";
f1();
f2();
f3();
--- /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 chdir() to the directory in which an Options
+file lives by using the __name__ value.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('bin', 'subdir')
+
+test.write('SConstruct', """\
+opts = Options('../bin/opts.cfg', ARGUMENTS)
+opts.Add('VARIABLE')
+Export("opts")
+SConscript('subdir/SConscript')
+""")
+
+SConscript_contents = """\
+Import("opts")
+env = Environment()
+opts.Update(env)
+print "VARIABLE =", repr(env['VARIABLE'])
+"""
+
+test.write(['bin', 'opts.cfg'], """\
+import os
+import os.path
+os.chdir(os.path.split(__name__)[0])
+execfile('opts2.cfg')
+""")
+
+test.write(['bin', 'opts2.cfg'], """\
+VARIABLE = 'opts2.cfg value'
+""")
+
+test.write(['subdir', 'SConscript'], SConscript_contents)
+
+expect = """\
+VARIABLE = 'opts2.cfg value'
+"""
+
+test.run(arguments = '-q -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 an Options file in a different directory can import
+a module in that directory.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+workpath = test.workpath('')
+
+test.subdir('bin', 'subdir')
+
+test.write('SConstruct', """\
+opts = Options('../bin/opts.cfg', ARGUMENTS)
+opts.Add('VARIABLE')
+Export("opts")
+SConscript('subdir/SConscript')
+""")
+
+SConscript_contents = """\
+Import("opts")
+env = Environment()
+opts.Update(env)
+print "VARIABLE =", env.get('VARIABLE')
+"""
+
+test.write(['bin', 'opts.cfg'], """\
+import sys
+from local_options import VARIABLE
+""" % locals())
+
+test.write(['bin', 'local_options.py'], """\
+VARIABLE = 'bin/local_options.py'
+""")
+
+test.write(['subdir', 'SConscript'], SConscript_contents)
+
+expect = "VARIABLE = bin/local_options.py\n"
+
+test.run(arguments = '-q -Q .', stdout = expect)
+
+test.pass_test()
test = TestSCons.TestSCons()
-test.subdir('work')
+test.subdir('work', ['work', 'sub'])
tar_output = test.workpath('work.tar')
-test.write(['work', 'copy.py'], """\
+test.write(['work', 'mycopy.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', 'mytar.py'], """\
+import sys
+import os.path
+
+def visit(arg, dirname, fnames):
+ fnames.sort()
+ for fn in fnames:
+ p = os.path.join(dirname, fn)
+ if os.path.isfile(p):
+ arg.write(open(p, 'rb').read())
+
+fp = open(sys.argv[1], 'wb')
+for s in sys.argv[2:]:
+ os.path.walk(s, visit, fp)
+""")
+
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')
+out1 = File('sub/f1.out')
+out2 = File('sub/f2.out')
+env.Command([out1, out1], 'sub/f1.in',
+ r'%(_python_)s mycopy.py 3 $TARGET $SOURCE')
+env.Command([out2, out2], 'sub/f2.in',
+ r'%(_python_)s mycopy.py 3 $TARGET $SOURCE')
-env.Tar(r'%(tar_output)s', Dir('.'))
+env.Command(r'%(tar_output)s', Dir('sub'),
+ r'%(_python_)s mytar.py $TARGET $SOURCE')
""" % locals())
-test.write(['work', 'f1.in'], "work/f1.in\n")
-test.write(['work', 'f2.in'], "work/f2.in\n")
+test.write(['work', 'sub', 'f1.in'], "work/sub/f1.in\n")
+test.write(['work', 'sub', 'f2.in'], "work/sub/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.must_match(['work', 'sub', 'f1.out'], "work/sub/f1.in\n")
+test.must_match(['work', 'sub', 'f2.out'], "work/sub/f2.in\n")
+test.must_match(tar_output, """\
+work/sub/f1.in
+work/sub/f1.in
+work/sub/f2.in
+work/sub/f2.in
+""")
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.
+#
+
+"""
+Verify that a failed build action with -j works as expected.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+try:
+ import threading
+except ImportError:
+ # if threads are not supported, then
+ # there is nothing to test
+ TestCmd.no_result()
+ sys.exit()
+
+
+test = TestSCons.TestSCons()
+
+# We want to verify that -j 2 starts precisely two jobs, the first of
+# which fails and the second of which succeeds, and then stops processing
+# due to the first build failure. To try to control the timing, the two
+# created build scripts use a pair of marker directories.
+#
+# The failure script waits until it sees the 'mycopy.started' directory
+# that indicates the successful script has, in fact, gotten started.
+# If we don't wait, then SCons could detect our script failure early
+# (typically if a high system load happens to delay SCons' ability to
+# start the next script) and then not start the successful script at all.
+#
+# The successful script waits until it sees the 'myfail.exiting' directory
+# that indicates the failure script has finished (with everything except
+# the final sys.exit(), that is). If we don't wait for that, then SCons
+# could detect our successful exit first (typically if a high system
+# load happens to delay the failure script) and start another job before
+# it sees the failure from the first script.
+
+test.write('myfail.py', r"""\
+import os.path
+import sys
+import time
+while not os.path.exists('mycopy.started'):
+ time.sleep(1)
+os.mkdir('myfail.exiting')
+sys.exit(1)
+""")
+
+test.write('mycopy.py', r"""\
+import os
+import sys
+import time
+os.mkdir('mycopy.started')
+open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
+while not os.path.exists('myfail.exiting'):
+ time.sleep(1)
+sys.exit(0)
+""")
+
+test.write('SConstruct', """
+MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE')
+Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
+env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail })
+env.Fail(target = 'f3', source = 'f3.in')
+env.MyCopy(target = 'f4', source = 'f4.in')
+env.MyCopy(target = 'f5', source = 'f5.in')
+env.MyCopy(target = 'f6', source = 'f6.in')
+""" % locals())
+
+test.write('f3.in', "f3.in\n")
+test.write('f4.in', "f4.in\n")
+test.write('f5.in', "f5.in\n")
+test.write('f6.in', "f6.in\n")
+
+test.run(arguments = '-j 2 .',
+ status = 2,
+ stderr = "scons: *** [f3] Error 1\n")
+
+test.must_not_exist(test.workpath('f3'))
+test.must_match(test.workpath('f4'), 'f4.in\n')
+test.must_not_exist(test.workpath('f5'))
+test.must_not_exist(test.workpath('f6'))
+
+
+
+test.pass_test()
#ifdef BAR
#define MAIN_OS "BAR"
#endif
-main()
+int
+main(int argc, char *argv[])
{
printf(INCLUDE_STRING, INCLUDE_OS);
printf(XXX_STRING, XXX_OS);
--- /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 env.Requires() method for specifying
+order-only prerequisites.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+def append_prereq_func(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in map(str, source):
+ fp.write(open(s, 'rb').read())
+ fp.write(open('prereq.out', 'rb').read())
+ fp.close()
+ return None
+append_prereq = Action(append_prereq_func)
+env = Environment()
+env.Requires('file.out', 'prereq.out')
+env.Command('file.out', 'file.in', append_prereq)
+env.Command('prereq.out', 'prereq.in', Copy('$TARGET', '$SOURCES'))
+""")
+
+test.write('file.in', "file.in 1\n")
+test.write('prereq.in', "prereq.in 1\n")
+
+# First: build file.out. prereq.out should be built first, and if
+# not, we'll get an error when the build action tries to use it to
+# build file.out.
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 1\n")
+test.must_match('file.out', "file.in 1\nprereq.in 1\n")
+
+# Update the prereq.out file. file.out should still be up to date because
+# prereq.out is not actually a dependency, so we don't detect the
+# underlying change.
+
+test.write('prereq.out', "prereq.out 2\n")
+
+test.up_to_date(arguments = 'file.out')
+
+# Now update the prereq.in file. Trying to rebuild file.out should
+# cause prereq.out to be updated because of the change, but file.out
+# should *not* be rebuilt because, again, prereq.out isn't actually
+# a dependency that causes rebuilds.
+
+test.write('prereq.in', "prereq.in 3\n")
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 3\n")
+test.must_match('file.out', "file.in 1\nprereq.in 1\n")
+
+# Now update file.in, which will cause file.out to be rebuilt, picking
+# up the change(s) to prereq.out of which we were previously oblivious.
+
+test.write('file.in', 'file.in 4\n')
+
+test.run(arguments = 'file.out')
+
+test.must_match('prereq.out', "prereq.in 3\n")
+test.must_match('file.out', "file.in 4\nprereq.in 3\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 use of a Scanner that searches a *PATH list doesn't create
+nodes for directories that don't exist, so they don't get picked up
+by DirScanner.
+
+Under the covers, this tests the behavior of the SCons.Node.FS.find_file()
+utility function that is used by the Scanner.Classic class to search
+directories in variables such as $CPPPATH.
+"""
+
+import os.path
+
+import TestSCons
+
+_python_ = TestSCons._python_
+
+test = TestSCons.TestSCons()
+
+subdir_SConscript = os.path.join('subdir', 'SConscript')
+subdir_foo = os.path.join('subdir', 'foo')
+subdir_foo_k = os.path.join('subdir', 'foo.k')
+
+test.subdir('subdir', 'inc1', 'inc2')
+
+inc2_include_h = test.workpath('inc2', 'include.h')
+
+test.write('build.py', r"""
+import os.path
+import string
+import sys
+path = string.split(sys.argv[1])
+input = open(sys.argv[2], 'rb')
+output = open(sys.argv[3], 'wb')
+
+def find_file(f):
+ if os.path.isabs(f):
+ return open(f, 'rb')
+ for dir in path:
+ p = dir + os.sep + f
+ if os.path.exists(p):
+ return open(p, 'rb')
+ return None
+
+def process(infp, outfp):
+ for line in infp.readlines():
+ if line[:8] == 'include ':
+ file = line[8:-1]
+ process(find_file(file), outfp)
+ else:
+ outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('SConstruct', """\
+def foo(target, source, env):
+ children = source[0].children()
+ children.sort(lambda a,b: cmp(a.name, b.name))
+ fp = open(str(target[0]), 'wb')
+ for c in children:
+ fp.write('%s\\n' % c)
+ fp.close()
+Command('list.out', 'subdir', foo, source_scanner = DirScanner)
+SConscript('subdir/SConscript')
+""")
+
+test.write(['subdir', 'SConscript'], """\
+import SCons.Scanner
+kscan = SCons.Scanner.Classic(name = 'kfile',
+ suffixes = ['.k'],
+ path_variable = 'KPATH',
+ regex = r'^include\s+(\S+)$')
+
+env = Environment(KPATH=['.', '..'])
+env.Append(SCANNERS = kscan)
+
+env.Command('foo', 'foo.k', r'%(_python_)s build.py "$KPATH" $SOURCES $TARGET')
+""" % locals())
+
+test.write(['subdir', 'foo.k'], """\
+subdir/foo.k
+include inc1/include.h
+include %(inc2_include_h)s
+""" % locals())
+
+test.write(['inc1', 'include.h'], """\
+inc1/include.h
+""")
+
+test.write(['inc2', 'include.h'], """\
+inc2/include.h
+""")
+
+test.run(arguments = '.')
+
+test.must_match('subdir/foo', """\
+subdir/foo.k
+inc1/include.h
+inc2/include.h
+""")
+
+test.must_match('list.out', """\
+%(subdir_SConscript)s
+%(subdir_foo)s
+%(subdir_foo_k)s
+""" % locals())
+
+test.pass_test()
test.write('SConstruct', """
Execute(Touch('f1'))
+Execute(Touch(File('f1-File')))
def cat(env, source, target):
target = str(target[0])
source = map(str, source)
""")
test.write('f1', "f1\n")
+test.write('f1-File', "f1-File\n")
test.write('f2.in', "f2.in\n")
test.write('f5.in', "f5.in\n")
test.write('f6.in', "f6.in\n")
-oldtime = os.path.getmtime(test.workpath('f1'))
+old_f1_time = os.path.getmtime(test.workpath('f1'))
+old_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
-expect = test.wrap_stdout(read_str = 'Touch("f1")\n',
+expect = test.wrap_stdout(read_str = """\
+Touch("f1")
+Touch("f1-File")
+""",
build_str = """\
cat(["f2.out"], ["f2.in"])
Touch("f3")
test.sleep(2)
-newtime = os.path.getmtime(test.workpath('f1'))
-test.fail_test(oldtime != newtime)
+new_f1_time = os.path.getmtime(test.workpath('f1'))
+test.fail_test(old_f1_time != new_f1_time)
+new_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
+test.fail_test(old_f1_File_time != new_f1_File_time)
test.must_not_exist(test.workpath('f2.out'))
test.must_not_exist(test.workpath('f3'))
test.run()
-newtime = os.path.getmtime(test.workpath('f1'))
-test.fail_test(oldtime == newtime)
+new_f1_time = os.path.getmtime(test.workpath('f1'))
+test.fail_test(old_f1_time == new_f1_time)
+new_f1_File_time = os.path.getmtime(test.workpath('f1-File'))
+test.fail_test(old_f1_File_time == new_f1_File_time)
test.must_match('f2.out', "f2.in\n")
test.must_exist(test.workpath('f3'))
--- /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 --implicit-cache works correctly in conjonction with a
+builder that produces multiple targets.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import os.path
+
+def emitter(target, source, env):
+ tgt0 = target[0].abspath
+ base,ext = os.path.splitext(tgt0)
+ target.append(base + '.b')
+ return(target, source)
+
+
+def source_scan(node, env, path):
+ path = node.abspath
+ base,ext = os.path.splitext(path)
+ return [base + '.lib']
+
+
+env = Environment()
+env['BUILDERS']['DualTarget'] = Builder(
+ action = Action(
+ [
+ Copy( '$TARGET', '$SOURCE' ),
+ Copy( '${TARGET.base}.b', '$SOURCE' ),
+ ],
+ ),
+ suffix = '.a',
+ src_suffix = '.cpp',
+ single_source = True,
+ emitter=emitter,
+ source_scanner=Scanner(source_scan),
+ )
+
+env.Command( 'x.cpp', '', Touch('$TARGET') )
+env.Command( 'x.lib', '', Touch('$TARGET') )
+
+env.DualTarget('x.cpp')
+""" % locals())
+
+test.must_not_exist('x.cpp')
+test.must_not_exist('x.lib')
+test.must_not_exist('x.a')
+test.must_not_exist('x.b')
+
+# Build everything first.
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') == -1)
+
+# Double check that targets are not rebuilt.
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt even with --implicit-cache
+test.run(arguments = '--implicit-cache x.a')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt even with --implicit-cache
+# a second time.
+test.run(arguments = '--implicit-cache x.a')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check that targets are not rebuilt if we reran without
+# --implicit-cache
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Double check again
+test.run(arguments = '.')
+test.must_exist('x.cpp')
+test.must_exist('x.lib')
+test.must_exist('x.a')
+test.must_exist('x.b')
+
+test.fail_test(string.find(test.stdout(), 'Copy') != -1)
+
+# Then only of the targets using --implicit-cache
+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 files are correctly located in the build directory even when
+Scons does not have a global view of all targets.
+
+Sometimes, it might be interesting to not tell scons about every
+targets. For example, one might not read in all the SConscipts of a
+hierarchical build for a particular invocation of scons. This would be
+done to speed-up a partial rebuild when the developer knows that only
+a subset of the targets need to be rebuilt.
+"""
+
+import string
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.subdir('dir1')
+test.subdir('dir2')
+
+test.write('SConstruct', """\
+opts = Options()
+opts.AddOptions(
+ BoolOption('view_all_dependencies', 'View all dependencies', True),
+ BoolOption('duplicate', 'Duplicate sources to build dir', True)
+)
+
+env = Environment(options=opts)
+Export('env')
+
+SConscript(dirs='.', build_dir='build', duplicate=env['duplicate'])
+""" % locals())
+
+
+test.write('SConscript', """\
+Import('env')
+
+if env['view_all_dependencies']:
+ SConscript(dirs='dir1')
+
+SConscript(dirs='dir2')
+""" % locals())
+
+test.write('dir1/SConscript', """\
+Import('env')
+
+env.Command('x.cpp', '', Touch('$TARGET'))
+
+env.Object(env.File('x.cpp'))
+""" % locals())
+
+test.write('dir2/SConscript', """\
+Import('env')
+
+env.Object(env.File('#/build/dir1/x.cpp'))
+""" % locals())
+
+test.must_not_exist('build/dir1/x.cpp')
+
+
+############################################################
+#
+# Test without duplication
+#
+
+# Build everything first.
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# Double check that targets are not rebuilt.
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# Clean-up only the object file
+test.run(arguments = 'duplicate=False view_all_dependencies=False -c .')
+test.must_exist('build/dir1/x.cpp')
+
+# Rebuild the only object file without seeing all the dependencies.
+test.run(arguments = 'duplicate=False view_all_dependencies=False .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# Double check that targets are not rebuilt without and with all the
+# dependencies.
+test.run(arguments = 'duplicate=False view_all_dependencies=False .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+test.run(arguments = 'duplicate=False view_all_dependencies=True .')
+test.must_exist('build/dir1/x.cpp')
+test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# Clean-up everything.
+test.run(arguments = 'duplicate=False view_all_dependencies=True -c .')
+test.must_not_exist('build/dir1/x.cpp')
+
+
+############################################################
+#
+# Test with duplication
+#
+# FIXME: This can not work for now because there is no way that SCons
+# can differentiate between a source that no longer exists and a file
+# that has a builder that scons does not know about because scons has
+# not parsed all the SConscript of a given project.
+#
+
+# # Build everything first.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# # Double check that targets are not rebuilt.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# # Clean-up only the object file
+# test.run(arguments = 'duplicate=True view_all_dependencies=False -c .')
+# test.must_exist('build/dir1/x.cpp')
+
+# # Rebuild the only object file without seeing all the dependencies.
+# test.run(arguments = 'duplicate=True view_all_dependencies=False .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") != -1)
+
+# # Double check that targets are not rebuilt without and with all the
+# # dependencies.
+# test.run(arguments = 'duplicate=True view_all_dependencies=False .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# test.run(arguments = 'duplicate=True view_all_dependencies=True .')
+# test.must_exist('build/dir1/x.cpp')
+# test.fail_test(string.find(test.stdout(), "`.' is up to date.") == -1)
+
+# # Clean-up everything.
+# test.run(arguments = 'duplicate=True view_all_dependencies=True -c .')
+# test.must_not_exist('build/dir1/x.cpp')
+
+
+test.pass_test()
test.fail_test(start2 < finish1)
-# Test that a failed build with -j works properly.
-
-test.write('mycopy.py', r"""\
-import sys
-import time
-time.sleep(1)
-open(sys.argv[1], 'wb').write(open(sys.argv[2], 'rb').read())
-""")
-
-test.write('myfail.py', r"""\
-import sys
-sys.exit(1)
-""")
-
-test.write('SConstruct', """
-MyCopy = Builder(action = r'%(_python_)s mycopy.py $TARGET $SOURCE')
-Fail = Builder(action = r'%(_python_)s myfail.py $TARGETS $SOURCE')
-env = Environment(BUILDERS = { 'MyCopy' : MyCopy, 'Fail' : Fail })
-env.Fail(target = 'f3', source = 'f3.in')
-env.MyCopy(target = 'f4', source = 'f4.in')
-env.MyCopy(target = 'f5', source = 'f5.in')
-env.MyCopy(target = 'f6', source = 'f6.in')
-""" % locals())
-
-test.write('f3.in', "f3.in\n")
-test.write('f4.in', "f4.in\n")
-test.write('f5.in', "f5.in\n")
-test.write('f6.in', "f6.in\n")
-
-test.run(arguments = '-j 2 .',
- status = 2,
- stderr = "scons: *** [f3] Error 1\n")
-
-test.fail_test(os.path.exists(test.workpath('f3')))
-test.fail_test(test.read(test.workpath('f4')) != 'f4.in\n')
-test.fail_test(os.path.exists(test.workpath('f5')))
-test.fail_test(os.path.exists(test.workpath('f6')))
test.pass_test()
test.write('bar.c', """
#include "bar.h"
+int local = 1;
""")
test.write('foo.h', """
test.write('bar.c', """
#include "bar.h"
+int local = 1;
""")
test.write('foo.h', """
test.write('bar.c', """
#include "bar.h"
+int local = 1;
""")
test.write('foo.h', """
test.write('Bar.c', """
#include "Bar.h"
+int local = 1;
""")
test.write('Foo.h', """
test.write('Bar.c', """
#include "Bar.h"
+int local = 1;
""")
test.write('Foo.h', """
test.write('bar.c', """
#include "bar.h"
+int local = 1;
""")
test.write('foo.h', """
test.must_exist( 'src-1.0.zip' )
test.must_exist( 'src-1.0.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' ],
+ target = 'src.zip' )
+""")
+
+test.run(arguments='', stderr = None)
+
+test.must_exist( 'src.zip' )
+test.must_exist( 'src-1.0.tar.gz' )
+
+
+
test.pass_test()
import os
import TestSCons
-machine = TestSCons.machine
_python_ = TestSCons._python_
test = TestSCons.TestSCons()
)
""" % locals())
-test.run(arguments='', stderr = None)
+expect = """
+scons: *** Setting target is not supported for rpm.
+""" + test.python_file_line(test.workpath('SConstruct'), 24)
-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.run(arguments='', status=2, stderr=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 "ghost" entries in the .sconsign file don't have Nodes
+created for them on subsequent runs (which would cause errors
+when scanning directories).
+"""
+
+import os.path
+
+d_current_txt = os.path.join('d', 'current.txt')
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+# This test case is from Morten Elo Petersen. It's harder because
+# blib-case1only actually exists in the build dir after the -c, so the
+# Dir scanner finds it and adds it to the dir's entries.
+# Unfortunately FS.py's File.exists() method checks it later and finds
+# it looks like a stale build copy of a missing source, so it deletes
+# it. And then it's later discovered to be missing since it's still
+# in the dir's entries list. The fix for this is to test for missing
+# source files in the dir scanner (Scanner/Dir.py), and delete them
+# (just like File.exists() does if they're found in the build dir
+# rather than making entries for them.
+
+test.write('SConstruct', """\
+def cat(target, source, env):
+ fp = open(str(target[0]), 'wb')
+ for s in source:
+ fp.write(open(str(s), 'rb').read())
+ fp.close()
+env=Environment()
+Export('env')
+env['BUILDERS']['Cat']=Builder(action=cat, multi=1)
+SConscript('src/SConscript',build_dir='build')
+""")
+
+test.subdir('src')
+test.write(['src', 'SConscript'], """\
+Import('env')
+if ARGUMENTS['case']=='1':
+ A=env.Cat('#build/lib/blib-case1only','src.txt')
+ B=env.Cat('#build/lib/blibB','#build/lib/blib-case1only')
+if ARGUMENTS['case']=='2':
+ A=env.Cat('case2only','src.txt')
+ B=env.Cat('#build/lib/blibB.txt','case2only')
+env.Alias('go','#build/lib')
+""")
+
+test.write(['src', 'src.txt'], "anything")
+
+test.run(arguments="-Q go case=1")
+test.must_exist('build/lib/blib-case1only')
+test.run(arguments="-Q go case=2")
+test.run(arguments="-Q go case=2 -c")
+test.run(arguments="-Q go case=2")
+
+
+#############################################################################
+# This test case is from Jason Orendorff.
+# Checking for existence before making nodes for things found in the
+# .sconsign fixes this one.
+
+test.write('SConstruct', """\
+Command("d/current.txt", [], [Touch("$TARGET")])
+if ARGUMENTS.has_key('pass') and ARGUMENTS['pass'] == '1':
+ Command("d/obsolete.txt", [], [Touch("$TARGET")])
+Command("installer.exe", ['d'], [Touch("$TARGET")])
+""")
+
+test.run(arguments='-Q pass=1')
+
+# Now delete the created files
+test.unlink(['d', 'obsolete.txt'])
+test.unlink(['d', 'current.txt'])
+test.rmdir(['d'])
+
+# Run again, as pass 2
+expect = """Touch("%(d_current_txt)s")
+Touch("installer.exe")
+""" % locals()
+
+test.run(arguments='-Q pass=2', stdout=expect)
+
+test.pass_test()
"""
import os.path
+import re
import TestSCons
import TestSConsign
CC = test.detect('CC', norm=1)
CC_dir, CC_file = os.path.split(CC)
+CC_dir = re.escape(os.path.normcase(CC_dir))
+CC_file = re.escape(CC_file)
# Note: We don't use os.path.join() representations of the file names
# in the expected output because paths in the .sconsign files are
using the signatures in an SConsignFile().
"""
-import os.path
-
+import TestSCons
import TestSConsign
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
-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 = TestSConsign.TestSConsign(match = TestSConsign.match_re)
test.subdir('sub1', 'sub2')
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py: %s\n' % sys.argv)
+
+def find_file(f):
+ for dir in path:
+ p = dir + os.sep + f
+ if os.path.exists(p):
+ return open(p, 'rb')
+ return None
+
+def process(infp, outfp):
+ for line in infp.readlines():
+ m = re.match('#include <(.*)>', line)
+ if m:
+ file = m.group(1)
+ process(find_file(file), outfp)
+ else:
+ outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py: %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
# 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.
test.write(['SConstruct'], """\
SConsignFile()
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+ OBJSUFFIX = '.obj',
+ CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+ LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
env1.Program('sub1/hello.c')
env2 = env1.Clone(CPPPATH = ['sub2'])
env2.Program('sub2/hello.c')
-""")
+""" % locals())
-test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("sub1/hello.c\n");
- exit (0);
-}
+test.write(['sub1', 'hello.c'], r"""
+sub1/hello.c
""")
-test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
+test.write(['sub2', 'hello.c'], r"""
#include <inc1.h>
#include <inc2.h>
int
test.run_sconsign(arguments = ".sconsign",
stdout = r"""=== .:
SConstruct: None \d+ \d+
-=== %(CC_dir)s:
-%(CC_file)s: %(sig_re)s \d+ \d+
=== sub1:
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.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+
test.run_sconsign(arguments = "--raw .sconsign",
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.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.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}
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
csig: %(sig_re)s
timestamp: \d+
size: \d+
- %(LINK)s:
- csig: %(sig_re)s
- timestamp: \d+
- size: \d+
action: %(sig_re)s \[.*\]
hello.obj:
csig: %(sig_re)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+
- %(LINK)s:
- csig: %(sig_re)s
- timestamp: \d+
- size: \d+
action: %(sig_re)s \[.*\]
hello.obj:
csig: %(sig_re)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
stdout = r"""=== .:
SConstruct:
csig: None
-=== %(CC_dir)s:
-%(CC_file)s:
- csig: %(sig_re)s
=== sub1:
hello.c:
csig: %(sig_re)s
stdout = r"""=== .:
SConstruct:
size: \d+
-=== %(CC_dir)s:
-%(CC_file)s:
- size: \d+
=== sub1:
hello.c:
size: \d+
stdout = r"""=== .:
SConstruct:
timestamp: \d+
-=== %(CC_dir)s:
-%(CC_file)s:
- timestamp: \d+
=== sub1:
hello.c:
timestamp: \d+
test.run_sconsign(arguments = "-e hello.obj .sconsign",
stdout = r"""=== .:
-=== %(CC_dir)s:
=== sub1:
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: %(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())
+ stderr = r"""sconsign: no entry `hello.obj' in `\.'
+""")
test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign",
stdout = r"""=== .:
-=== %(CC_dir)s:
=== sub1:
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: %(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",
respectively).
"""
+import TestSCons
import TestSConsign
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
-CC = test.detect('CC', norm=1)
-LINK = test.detect('LINK', norm=1)
-if LINK is None: LINK = CC
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
# Note: We don't use os.path.join() representations of the file names
# in the expected output because paths in the .sconsign files are
sub1_hello_c = 'sub1/hello.c'
sub1_hello_obj = 'sub1/hello.obj'
-def re_sep(*args):
- import os.path
- import re
- return re.escape(apply(os.path.join, args))
-
test.subdir('sub1', 'sub2')
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py: %s\n' % sys.argv)
+
+def find_file(f):
+ for dir in path:
+ p = dir + os.sep + f
+ if os.path.exists(p):
+ return open(p, 'rb')
+ return None
+
+def process(infp, outfp):
+ for line in infp.readlines():
+ m = re.match('#include <(.*)>', line)
+ if m:
+ file = m.group(1)
+ process(find_file(file), outfp)
+ else:
+ outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py: %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
test.write('SConstruct', """
SConsignFile(None)
SourceSignatures('timestamp')
TargetSignatures('content')
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+ OBJSUFFIX = '.obj',
+ CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+ LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
env1.Program('sub1/hello.c')
env2 = env1.Clone(CPPPATH = ['sub2'])
env2.Program('sub2/hello.c')
-""")
+""" % locals())
test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("sub1/hello.c\n");
- exit (0);
-}
+sub1/hello.c
""")
test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
#include <inc1.h>
#include <inc2.h>
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("sub2/goodbye.c\n");
- exit (0);
-}
+sub2/hello.c
""")
test.write(['sub2', 'inc1.h'], r"""\
test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/.sconsign",
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 = 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())
.sconsign file in each directory (SConsignFile(None)).
"""
+import TestSCons
import TestSConsign
-test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
+_python_ = TestSCons._python_
-CC = test.detect('CC', norm=1)
-LINK = test.detect('LINK', norm=1)
-if LINK is None: LINK = CC
+test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
test.subdir('sub1', 'sub2')
+test.write('fake_cc.py', r"""
+import os.path
+import re
+import string
+import sys
+
+path = string.split(sys.argv[1])
+output = open(sys.argv[2], 'wb')
+input = open(sys.argv[3], 'rb')
+
+output.write('fake_cc.py: %s\n' % sys.argv)
+
+def find_file(f):
+ for dir in path:
+ p = dir + os.sep + f
+ if os.path.exists(p):
+ return open(p, 'rb')
+ return None
+
+def process(infp, outfp):
+ for line in infp.readlines():
+ m = re.match('#include <(.*)>', line)
+ if m:
+ file = m.group(1)
+ process(find_file(file), outfp)
+ else:
+ outfp.write(line)
+
+process(input, output)
+
+sys.exit(0)
+""")
+
+test.write('fake_link.py', r"""
+import sys
+
+output = open(sys.argv[1], 'wb')
+input = open(sys.argv[2], 'rb')
+
+output.write('fake_link.py: %s\n' % sys.argv)
+
+output.write(input.read())
+
+sys.exit(0)
+""")
+
# 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.
test.write(['SConstruct'], """
SConsignFile(None)
-env1 = Environment(PROGSUFFIX = '.exe', OBJSUFFIX = '.obj')
+env1 = Environment(PROGSUFFIX = '.exe',
+ OBJSUFFIX = '.obj',
+ CCCOM = r'%(_python_)s fake_cc.py sub2 $TARGET $SOURCE',
+ LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCE')
env1.Program('sub1/hello.c')
env2 = env1.Clone(CPPPATH = ['sub2'])
env2.Program('sub2/hello.c')
-""")
+""" % locals())
test.write(['sub1', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("sub1/hello.c\n");
- exit (0);
-}
+sub1/hello.c
""")
test.write(['sub2', 'hello.c'], r"""\
-#include <stdio.h>
-#include <stdlib.h>
#include <inc1.h>
#include <inc2.h>
-int
-main(int argc, char *argv[])
-{
- argv[argc++] = "--";
- printf("sub2/goodbye.c\n");
- exit (0);
-}
+sub2/hello.c
""")
test.write(['sub2', 'inc1.h'], r"""\
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()
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())
csig: %(sig_re)s
timestamp: \d+
size: \d+
- %(LINK)s:
- csig: %(sig_re)s
- timestamp: \d+
- size: \d+
action: %(sig_re)s \[.*\]
hello.obj:
csig: %(sig_re)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 = "-e hello.obj sub1/.sconsign",
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 = 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())
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+
%(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())
import TestSCons
+_python_ = TestSCons._python_
+
test = TestSCons.TestSCons()
#if os.path.exists('sconsign.py'):
test.subdir('src', ['src', 'sub'])
+test.write('fake_cc.py', """\
+import sys
+ofp = open(sys.argv[1], 'wb')
+ofp.write('fake_cc.py: %s\\n' % sys.argv)
+for s in sys.argv[2:]:
+ ofp.write(open(s, 'rb').read())
+""")
+
+test.write('fake_link.py', """\
+import sys
+ofp = open(sys.argv[1], 'wb')
+ofp.write('fake_link.py: %s\\n' % sys.argv)
+for s in sys.argv[2:]:
+ ofp.write(open(s, 'rb').read())
+""")
+
test.write('SConstruct', """\
SConsignFile(None)
TargetSignatures('content')
-env = Environment()
+env = Environment(PROGSUFFIX = '.exe',
+ OBJSUFFIX = '.obj',
+ CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES',
+ LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES')
env.SConscript('src/SConstruct', exports=['env'])
env.Object('foo.c')
-""")
+""" % locals())
test.write(['src', 'SConstruct'], """\
SConsignFile(None)
TargetSignatures('content')
-env = Environment()
-p = env.Program('prog', ['main.c', '../foo%s', 'sub/bar.c'])
+env = Environment(PROGSUFFIX = '.exe',
+ OBJSUFFIX = '.obj',
+ CCCOM = r'%(_python_)s fake_cc.py $TARGET $SOURCES',
+ LINKCOM = r'%(_python_)s fake_link.py $TARGET $SOURCES')
+p = env.Program('prog', ['main.c', '../foo$OBJSUFFIX', 'sub/bar.c'])
env.Default(p)
-""" % TestSCons._obj)
+""" % locals())
test.write('foo.c', """\
-#include <stdio.h>
-#include <stdlib.h>
-void
-foo(void) {
- printf("foo.c\\n");
-}
+foo.c
""")
test.write(['src', 'main.c'], """\
-#include <stdio.h>
-#include <stdlib.h>
-extern void foo(void);
-extern void bar(void);
-int
-main(int argc, char *argv[]) {
- foo();
- bar();
- printf("src/main.c\\n");
- exit (0);
-}
+src/main.c
""")
test.write(['src', 'sub', 'bar.c'], """\
-#include <stdio.h>
-#include <stdlib.h>
-void
-bar(void) {
- printf("bar.c\\n");
-}
+src/sub/bar.c
""")
test.run()
-test.run(program=test.workpath('src', 'prog'),
- stdout="foo.c\nbar.c\nsrc/main.c\n")
+src_prog_exe = os.path.join('src', 'prog.exe')
+src_main_c = os.path.join('src', 'main.c')
+src_main_obj = os.path.join('src', 'main.obj')
+src_sub_bar_c = os.path.join('src', 'sub', 'bar.c')
+src_sub_bar_obj = os.path.join('src', 'sub', 'bar.obj')
+
+expect = """\
+fake_link.py: ['fake_link.py', '%(src_prog_exe)s', '%(src_main_obj)s', 'foo.obj', '%(src_sub_bar_obj)s']
+fake_cc.py: ['fake_cc.py', '%(src_main_obj)s', '%(src_main_c)s']
+src/main.c
+fake_cc.py: ['fake_cc.py', 'foo.obj', 'foo.c']
+foo.c
+fake_cc.py: ['fake_cc.py', '%(src_sub_bar_obj)s', '%(src_sub_bar_c)s']
+src/sub/bar.c
+""" % locals()
+
+if os.sep == '\\':
+ import string
+ expect = string.replace(expect, '\\', '\\\\')
+
+test.must_match(['src', 'prog.exe'], expect)
test.up_to_date(chdir='src', arguments = test.workpath())