From fa11b8d2fa2e3adc18588992ff869b1f1457c03f Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Tue, 20 Nov 2001 17:58:56 +0000 Subject: Crain: Finish LIBS, LIBPATH, CPPPATH --- src/engine/MANIFEST.in | 1 + src/engine/SCons/Builder.py | 16 ++++-- src/engine/SCons/BuilderTests.py | 19 ++++++- src/engine/SCons/Defaults.py | 21 +++++--- src/engine/SCons/Environment.py | 6 ++- src/engine/SCons/EnvironmentTests.py | 12 +++++ src/engine/SCons/Node/FS.py | 33 ++++++++++++- src/engine/SCons/Node/FSTests.py | 6 +++ src/engine/SCons/Node/NodeTests.py | 3 ++ src/engine/SCons/Node/__init__.py | 15 ++++-- src/engine/SCons/Scanner/C.py | 55 +++++++-------------- src/engine/SCons/Scanner/CTests.py | 47 +++++++++++------- src/engine/SCons/Scanner/Prog.py | 66 +++++++++++++++++++++++++ src/engine/SCons/Scanner/ProgTests.py | 93 +++++++++++++++++++++++++++++++++++ src/engine/SCons/Sig/MD5Tests.py | 4 +- src/engine/SCons/Sig/SigTests.py | 2 +- src/engine/SCons/Sig/__init__.py | 19 ++++++- src/engine/SCons/Util.py | 34 +++++++++++++ src/engine/SCons/UtilTests.py | 19 ++++++- test/CPPPATH.py | 37 +++++++++++++- test/LIBPATH.py | 31 ++++++++++-- test/Library.py | 11 ++--- 22 files changed, 457 insertions(+), 93 deletions(-) create mode 100644 src/engine/SCons/Scanner/Prog.py create mode 100644 src/engine/SCons/Scanner/ProgTests.py diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index a79a059..410b013 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -9,6 +9,7 @@ SCons/Node/__init__.py SCons/Node/FS.py SCons/Scanner/__init__.py SCons/Scanner/C.py +SCons/Scanner/Prog.py SCons/Sig/__init__.py SCons/Sig/MD5.py SCons/Sig/TimeStamp.py diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 4719951..fc372fa 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -136,7 +136,8 @@ class BuilderBase: prefix = '', suffix = '', src_suffix = '', - node_factory = SCons.Node.FS.default_fs.File): + node_factory = SCons.Node.FS.default_fs.File, + scanner = None): self.name = name self.action = Action(action) @@ -144,6 +145,7 @@ class BuilderBase: self.suffix = suffix self.src_suffix = src_suffix self.node_factory = node_factory + self.scanner = scanner if self.suffix and self.suffix[0] not in '.$': self.suffix = '.' + self.suffix if self.src_suffix and self.src_suffix[0] not in '.$': @@ -158,9 +160,10 @@ class BuilderBase: if not type(files) is type([]): files = [files] for f in files: - if type(f) == type(""): + if type(f) is types.StringType or isinstance(f, UserString): if pre and f[:len(pre)] != pre: - f = pre + f + path, fn = os.path.split(os.path.normpath(f)) + f = os.path.join(path, pre + fn) if suf: if f[-len(suf):] != suf: f = f + suf @@ -180,6 +183,8 @@ class BuilderBase: t.builder_set(self) t.env_set(env) t.add_source(slist) + if self.scanner: + t.scanner_set(self.scanner) for s in slist: s.env_set(env, 1) @@ -216,9 +221,10 @@ class MultiStepBuilder(BuilderBase): prefix = '', suffix = '', src_suffix = '', - node_factory = SCons.Node.FS.default_fs.File): + node_factory = SCons.Node.FS.default_fs.File, + scanner=None): BuilderBase.__init__(self, name, action, prefix, suffix, src_suffix, - node_factory) + node_factory, scanner) self.src_builder = src_builder def __call__(self, env, target = None, source = None): diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index 3910323..cc5b314 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -36,7 +36,6 @@ import unittest import TestCmd import SCons.Builder - # Initial setup of the common environment for all tests, # a temporary working directory containing a # script for writing arguments to an output file. @@ -269,6 +268,7 @@ class BuilderTestCase(unittest.TestCase): class Foo: pass def FooFactory(target): + global Foo return Foo(target) builder = SCons.Builder.Builder(node_factory = FooFactory) assert builder.node_factory is FooFactory @@ -347,6 +347,23 @@ class BuilderTestCase(unittest.TestCase): flag = 1 assert flag, "UserError should be thrown when we build targets with files of different suffixes." + def test_build_scanner(self): + """Testing ability to set a target scanner through a builder.""" + class TestScanner: + pass + scn = TestScanner() + builder=SCons.Builder.Builder(scanner=scn) + tgt = builder(env, target='foo', source='bar') + assert tgt.scanner == scn, tgt.scanner + + builder1 = SCons.Builder.Builder(action='foo', + src_suffix='.bar', + suffix='.foo') + builder2 = SCons.Builder.Builder(action='foo', + src_builder = builder1, + scanner = scn) + tgt = builder2(env, target='baz', source='test.bar test2.foo test3.txt') + assert tgt.scanner == scn, tgt.scanner if __name__ == "__main__": suite = unittest.makeSuite(BuilderTestCase, 'test_') diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index b330dae..0bc82eb 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -36,7 +36,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import SCons.Builder import SCons.Scanner.C - +import SCons.Scanner.Prog Object = SCons.Builder.Builder(name = 'Object', action = { '.c' : '$CCCOM', @@ -55,7 +55,8 @@ Program = SCons.Builder.Builder(name = 'Program', prefix = '$PROGPREFIX', suffix = '$PROGSUFFIX', src_suffix = '$OBJSUFFIX', - src_builder = Object) + src_builder = Object, + scanner = SCons.Scanner.Prog.ProgScan()) Library = SCons.Builder.Builder(name = 'Library', action = '$ARCOM', @@ -72,13 +73,13 @@ if os.name == 'posix': ConstructionEnvironment = { 'CC' : 'cc', 'CCFLAGS' : '', - 'CCCOM' : '$CC $CCFLAGS -c -o $TARGET $SOURCES', + 'CCCOM' : '$CC $CCFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'CXX' : 'c++', 'CXXFLAGS' : '$CCFLAGS', - 'CXXCOM' : '$CXX $CXXFLAGS -c -o $TARGET $SOURCES', + 'CXXCOM' : '$CXX $CXXFLAGS $_INCFLAGS -c -o $TARGET $SOURCES', 'LINK' : '$CXX', 'LINKFLAGS' : '', - 'LINKCOM' : '$LINK $LINKFLAGS -o $TARGET $SOURCES', + 'LINKCOM' : '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS', 'AR' : 'ar', 'ARFLAGS' : 'r', 'ARCOM' : '$AR $ARFLAGS $TARGET $SOURCES\nranlib $TARGET', @@ -94,6 +95,8 @@ if os.name == 'posix': 'LIBDIRSUFFIX' : '', 'LIBLINKPREFIX' : '-l', 'LIBLINKSUFFIX' : '', + 'INCPREFIX' : '-I', + 'INCSUFFIX' : '', 'ENV' : { 'PATH' : '/usr/local/bin:/bin:/usr/bin' }, } @@ -102,13 +105,13 @@ elif os.name == 'nt': ConstructionEnvironment = { 'CC' : 'cl', 'CCFLAGS' : '/nologo', - 'CCCOM' : '$CC $CCFLAGS /c $SOURCES /Fo$TARGET', + 'CCCOM' : '$CC $CCFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', 'CXX' : '$CC', 'CXXFLAGS' : '$CCFLAGS', - 'CXXCOM' : '$CXX $CXXFLAGS /c $SOURCES /Fo$TARGET', + 'CXXCOM' : '$CXX $CXXFLAGS $_INCFLAGS /c $SOURCES /Fo$TARGET', 'LINK' : 'link', 'LINKFLAGS' : '', - 'LINKCOM' : '$LINK $LINKFLAGS /out:$TARGET $SOURCES', + 'LINKCOM' : '$LINK $LINKFLAGS /out:$TARGET $_LIBDIRFLAGS $_LIBFLAGS $SOURCES', 'AR' : 'lib', 'ARFLAGS' : '/nologo', 'ARCOM' : '$AR $ARFLAGS /out:$TARGET $SOURCES', @@ -124,6 +127,8 @@ elif os.name == 'nt': 'LIBDIRSUFFIX' : '', 'LIBLINKPREFIX' : '', 'LIBLINKSUFFIX' : '$LIBSUFFIX', + 'INCPREFIX' : '/I', + 'INCSUFFIX' : '', 'ENV' : { 'PATH' : r'C:\Python20;C:\WINNT\system32;C:\WINNT;C:\Program Files\Microsoft Visual Studio\VC98\Bin\;', 'PATHEXT' : '.COM;.EXE;.BAT;.CMD', diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index e603b43..812eda7 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -81,7 +81,11 @@ class Environment: ( '_LIBDIRFLAGS', 'LIBPATH', 'LIBDIRPREFIX', - 'LIBDIRSUFFIX' ) ) + 'LIBDIRSUFFIX' ), + ( '_INCFLAGS', + 'CPPPATH', + 'INCPREFIX', + 'INCSUFFIX' ) ) def __init__(self, **kw): import SCons.Defaults diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 1fbdb8c..3341c83 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -269,6 +269,18 @@ class EnvironmentTestCase(unittest.TestCase): assert env.Dictionary('_LIBFLAGS')[2] == 'foobazbar', \ env.Dictionary('_LIBFLAGS')[2] + env = Environment(CPPPATH = [ 'foo', 'bar', 'baz' ], + INCPREFIX = 'foo', + INCSUFFIX = 'bar') + assert len(env.Dictionary('_INCFLAGS')) == 3, env.Dictionary('_INCFLAGS') + assert env.Dictionary('_INCFLAGS')[0] == 'foofoobar', \ + env.Dictionary('_INCFLAGS')[0] + assert env.Dictionary('_INCFLAGS')[1] == 'foobarbar', \ + env.Dictionary('_INCFLAGS')[1] + assert env.Dictionary('_INCFLAGS')[2] == 'foobazbar', \ + env.Dictionary('_INCFLAGS')[2] + + if __name__ == "__main__": suite = unittest.makeSuite(EnvironmentTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 7ee769e..75f7c5c 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -271,7 +271,7 @@ class Entry(SCons.Node.Node): return self.path def exists(self): - return os.path.exists(self.path) + return os.path.exists(self.abspath) def current(self): """If the underlying path doesn't exist, we know the node is @@ -338,7 +338,13 @@ class Dir(Entry): kids = map(lambda x, s=self: s.entries[x], filter(lambda k: k != '.' and k != '..', self.entries.keys())) - kids.sort() + def c(one, two): + if one.abspath < two.abspath: + return -1 + if one.abspath > two.abspath: + return 1 + return 0 + kids.sort(c) return kids def build(self): @@ -451,5 +457,28 @@ class File(Entry): self.add_dependency(scanner.scan(self.path_, self.env)) self.scanned = 1 + def __createDir(self): + # ensure that the directories for this node are + # created. + + listPaths = [] + strPath = self.abspath + while 1: + strPath, strFile = os.path.split(strPath) + if os.path.exists(strPath): + break + listPaths.append(strPath) + if not strFile: + break + listPaths.reverse() + for strPath in listPaths: + try: + os.mkdir(strPath) + except OSError: + pass + + def build(self): + self.__createDir() + Entry.build(self) default_fs = FS() diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 4305a32..fe35d76 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -301,6 +301,12 @@ class FSTestCase(unittest.TestCase): f1.scan() assert f1.depends[0].path_ == "d1/f1" + # Test building a file whose directory is not there yet... + f1 = fs.File(test.workpath("foo/bar/baz/ack")) + assert not f1.dir.exists() + f1.build() + assert f1.dir.exists() + #XXX test exists() #XXX test current() for directories diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py index 373571b..8c51e0b 100644 --- a/src/engine/SCons/Node/NodeTests.py +++ b/src/engine/SCons/Node/NodeTests.py @@ -296,10 +296,13 @@ class NodeTestCase(unittest.TestCase): nw = SCons.Node.Walker(n1) assert nw.next().name == "n4" assert nw.next().name == "n5" + assert nw.history.has_key(n2) assert nw.next().name == "n2" assert nw.next().name == "n6" assert nw.next().name == "n7" + assert nw.history.has_key(n3) assert nw.next().name == "n3" + assert nw.history.has_key(n1) assert nw.next().name == "n1" assert nw.next() == None diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 6364bf8..a6606a9 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -192,10 +192,15 @@ class Walker: returns the next node on the descent with each next() call. 'kids_func' is an optional function that will be called to get the children of a node instead of calling 'children'. + + This class does not get caught in node cycles caused, for example, + by C header file include loops. """ def __init__(self, node, kids_func=get_children): self.kids_func = kids_func self.stack = [Wrapper(node, self.kids_func)] + self.history = {} # used to efficiently detect and avoid cycles + self.history[node] = None def next(self): """Return the next node for this walk of the tree. @@ -206,10 +211,14 @@ class Walker: while self.stack: if self.stack[-1].kids: - self.stack.append(Wrapper(self.stack[-1].kids.pop(0), - self.kids_func)) + node = self.stack[-1].kids.pop(0) + if not self.history.has_key(node): + self.stack.append(Wrapper(node, self.kids_func)) + self.history[node] = None else: - return self.stack.pop().node + node = self.stack.pop().node + del self.history[node] + return node def is_done(self): return not self.stack diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index 0d74dc5..e2842e1 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -33,6 +33,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import SCons.Scanner import re import os.path +import SCons.Util angle_re = re.compile('^[ \t]*#[ \t]*include[ \t]+<([\\w./\\\\]+)>', re.M) quote_re = re.compile('^[ \t]*#[ \t]*include[ \t]+"([\\w./\\\\]+)"', re.M) @@ -44,28 +45,6 @@ def CScan(): s.name = "CScan" return s -def find_files(filenames, paths): - """ - find_files([str], [str]) -> [str] - - filenames - a list of filenames to find - paths - a list of paths to search in - - returns - the fullnames of the files - - Only the first fullname found is returned for each filename, and any - file that aren't found are ignored. - """ - fullnames = [] - for filename in filenames: - for path in paths: - fullname = os.path.join(path, filename) - if os.path.exists(fullname): - fullnames.append(fullname) - break - - return fullnames - def scan(filename, env, node_factory): """ scan(str, Environment) -> [str] @@ -87,22 +66,24 @@ def scan(filename, env, node_factory): dependencies. """ - if hasattr(env, "CPPPATH"): - paths = env.CPPPATH - else: + try: + paths = env.Dictionary("CPPPATH") + except KeyError: paths = [] - - file = open(filename) - contents = file.read() - file.close() - angle_includes = angle_re.findall(contents) - quote_includes = quote_re.findall(contents) + try: + file = open(filename) + contents = file.read() + file.close() - source_dir = os.path.dirname(filename) - - deps = (find_files(angle_includes, paths + [source_dir]) - + find_files(quote_includes, [source_dir] + paths)) + angle_includes = angle_re.findall(contents) + quote_includes = quote_re.findall(contents) - deps = map(node_factory, deps) - return deps + source_dir = os.path.dirname(filename) + + return (SCons.Util.find_files(angle_includes, paths + [source_dir], + node_factory) + + SCons.Util.find_files(quote_includes, [source_dir] + paths, + node_factory)) + except OSError: + return [] diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index 8b8aeb5..e6bd29e 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -27,6 +27,7 @@ import TestCmd import SCons.Scanner.C import unittest import sys +import os.path test = TestCmd.TestCmd(workdir = '') @@ -88,55 +89,65 @@ for h in headers: # define some helpers: class DummyEnvironment: - pass + def __init__(self, listCppPath): + self.path = listCppPath + + def Dictionary(self, *args): + if not args: + return { 'CPPPATH': self.path } + elif len(args) == 1 and args[0] == 'CPPPATH': + return self.path + else: + raise KeyError, "Dummy environment only has CPPPATH attribute." def deps_match(deps, headers): - return deps.sort() == map(test.workpath, headers).sort() + deps = map(str, deps) + headers = map(test.workpath, headers) + deps.sort() + headers.sort() + return map(os.path.normpath, deps) == \ + map(os.path.normpath, headers) # define some tests: class CScannerTestCase1(unittest.TestCase): def runTest(self): - env = DummyEnvironment + env = DummyEnvironment([]) s = SCons.Scanner.C.CScan() deps = s.scan(test.workpath('f1.cpp'), env) - self.failUnless(deps_match(deps, ['f1.h', 'f2.h'])) + self.failUnless(deps_match(deps, ['f1.h', 'f2.h']), map(str, deps)) class CScannerTestCase2(unittest.TestCase): def runTest(self): - env = DummyEnvironment - env.CPPPATH = [test.workpath("d1")] + env = DummyEnvironment([test.workpath("d1")]) s = SCons.Scanner.C.CScan() deps = s.scan(test.workpath('f1.cpp'), env) headers = ['f1.h', 'd1/f2.h'] - self.failUnless(deps_match(deps, headers)) + self.failUnless(deps_match(deps, headers), map(str, deps)) class CScannerTestCase3(unittest.TestCase): def runTest(self): - env = DummyEnvironment - env.CPPPATH = [test.workpath("d1")] + env = DummyEnvironment([test.workpath("d1")]) s = SCons.Scanner.C.CScan() deps = s.scan(test.workpath('f2.cpp'), env) - headers = ['f1.h', 'd1/f2.h', 'd1/d2/f1.h'] - self.failUnless(deps_match(deps, headers)) - + headers = ['f1.h', 'd1/f1.h', 'd1/d2/f1.h'] + self.failUnless(deps_match(deps, headers), map(str, deps)) class CScannerTestCase4(unittest.TestCase): def runTest(self): - env = DummyEnvironment - env.CPPPATH = [test.workpath("d1"), test.workpath("d1/d2")] + env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")]) s = SCons.Scanner.C.CScan() deps = s.scan(test.workpath('f2.cpp'), env) - headers = ['f1.h', 'd1/f2.h', 'd1/d2/f1.h', 'd1/d2/f4.h'] - self.failUnless(deps_match(deps, headers)) + headers = ['f1.h', 'd1/f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h'] + self.failUnless(deps_match(deps, headers), map(str, deps)) class CScannerTestCase5(unittest.TestCase): def runTest(self): - env = DummyEnvironment + env = DummyEnvironment([]) s = SCons.Scanner.C.CScan() deps = s.scan(test.workpath('f3.cpp'), env) headers = ['f1.h', 'f2.h', 'f3.h', 'd1/f1.h', 'd1/f2.h', 'd1/f3.h'] - self.failUnless(deps_match(deps, headers)) + self.failUnless(deps_match(deps, headers), map(str, deps)) def suite(): suite = unittest.TestSuite() diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py new file mode 100644 index 0000000..f9d352c --- /dev/null +++ b/src/engine/SCons/Scanner/Prog.py @@ -0,0 +1,66 @@ +# +# Copyright (c) 2001 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import SCons.Scanner +import SCons.Node.FS +import SCons.Util + +def ProgScan(): + """Return a Scanner instance for scanning executable files + for static-lib dependencies""" + s = SCons.Scanner.Scanner(scan, SCons.Node.FS.default_fs.File) + s.name = "ProgScan" + return s + +def scan(filename, env, node_factory): + """ + This scanner scans program files for static-library + dependencies. It will search the LIBPATH environment variable + for libraries specified in the LIBS variable, returning any + files it finds as dependencies. + """ + + try: + paths = env.Dictionary("LIBPATH") + except KeyError: + paths = [] + + try: + libs = env.Dictionary("LIBS") + except KeyError: + libs = [] + + try: + prefix = env.Dictionary("LIBPREFIX") + except KeyError: + prefix='' + + try: + suffix = env.Dictionary("LIBSUFFIX") + except KeyError: + suffix='' + + libs = map(lambda x, s=suffix, p=prefix: p + x + s, libs) + return SCons.Util.find_files(libs, paths, node_factory) diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py new file mode 100644 index 0000000..6c3f5e4 --- /dev/null +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -0,0 +1,93 @@ +# +# Copyright (c) 2001 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import TestCmd +import SCons.Scanner.Prog +import unittest +import sys +import os.path + +test = TestCmd.TestCmd(workdir = '') + +test.subdir('d1', ['d1', 'd2']) + +libs = [ 'l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ] + +for h in libs: + test.write(h, " ") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, **kw): + self._dict = kw + self._dict['LIBSUFFIX'] = '.lib' + + def Dictionary(self, *args): + if not args: + return self._dict + elif len(args) == 1: + return self._dict[args[0]] + else: + return map(lambda x, s=self: s._dict[x], args) + +def deps_match(deps, libs): + deps=map(str, deps) + deps.sort() + libs.sort() + return map(os.path.normpath, deps) == \ + map(os.path.normpath, + map(test.workpath, libs)) + +# define some tests: + +class ProgScanTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgScan() + deps = s.scan('dummy', env) + assert deps_match(deps, ['l1.lib']), map(str, deps) + +class ProgScanTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=map(test.workpath, + ["", "d1", "d1/d2" ]), + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgScan() + deps = s.scan('dummy', env) + assert deps_match(deps, ['l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]), map(str, deps) + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ProgScanTestCase1()) + suite.addTest(ProgScanTestCase2()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) diff --git a/src/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py index 2d42fc0..e74d339 100644 --- a/src/engine/SCons/Sig/MD5Tests.py +++ b/src/engine/SCons/Sig/MD5Tests.py @@ -25,6 +25,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import sys import unittest +import string from SCons.Sig.MD5 import current, collect, signature, to_string, from_string @@ -83,7 +84,8 @@ class MD5TestCase(unittest.TestCase): try: signature('string') except AttributeError, e: - assert str(e) == "unable to fetch contents of 'string': 'string' object has no attribute 'get_contents'", e + # the error string should begin with "unable to fetch contents of 'string': " + assert string.find(str(e), "unable to fetch contents of 'string':") == 0 else: raise AttributeError, "unexpected get_contents() attribute" diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index 0870c93..19816d5 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -211,7 +211,7 @@ class SigTestBase: self.failUnless(current(calc, nodes[4])) self.failUnless(current(calc, nodes[5])) self.failUnless(not current(calc, nodes[6]), "modified directly") - self.failUnless(current(calc, nodes[7]), "indirect source modified") + self.failUnless(not current(calc, nodes[7]), "indirect source modified") self.failUnless(not current(calc, nodes[8]), "modified directory") self.failUnless(not current(calc, nodes[9]), "direct source modified") self.failUnless(not current(calc, nodes[10]), "indirect source modified") diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index 43e3b8a..fb34fa3 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -31,7 +31,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os.path import string - +import SCons.Node #XXX Get rid of the global array so this becomes re-entrant. sig_files = [] @@ -154,7 +154,22 @@ class Calculator: return None #XXX If configured, use the content signatures from the #XXX .sconsign file if the timestamps match. - sigs = map(lambda n,s=self: s.get_signature(n), node.children()) + + # Collect the signatures for ALL the nodes that this + # node depends on. Just collecting the direct + # dependants is not good enough, because + # the signature of a non-derived file does + # not include the signatures of its psuedo-sources + # (e.g. the signature for a .c file does not include + # the signatures of the .h files that it includes). + walker = SCons.Node.Walker(node) + sigs = [] + while 1: + child = walker.next() + if child is None: break + if child is node: continue # skip the node itself + sigs.append(self.get_signature(child)) + if node.builder: sigs.append(self.module.signature(node.builder_sig_adapter())) return self.module.collect(filter(lambda x: not x is None, sigs)) diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index daeb243..1d42d18 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -221,3 +221,37 @@ def scons_subst(strSubst, locals, globals): """ cmd_list = scons_subst_list(strSubst, locals, globals) return string.join(map(string.join, cmd_list), '\n') + +def find_files(filenames, paths, + node_factory = SCons.Node.FS.default_fs.File): + """ + find_files([str], [str]) -> [nodes] + + filenames - a list of filenames to find + paths - a list of paths to search in + + returns - the nodes created from the found files. + + Finds nodes corresponding to either derived files or files + that exist already. + + Only the first fullname found is returned for each filename, and any + file that aren't found are ignored. + """ + nodes = [] + for filename in filenames: + for path in paths: + fullname = os.path.join(path, filename) + try: + node = node_factory(fullname) + # Return true of the node exists or is a derived node. + if node.builder or \ + (isinstance(node, SCons.Node.FS.Entry) and node.exists()): + nodes.append(node) + break + except TypeError: + # If we find a directory instead of a file, we + # don't care + pass + + return nodes diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 01d3031..e8a6eca 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -30,8 +30,8 @@ import sys import unittest import SCons.Node import SCons.Node.FS -from SCons.Util import scons_str2nodes, scons_subst, PathList, scons_subst_list - +from SCons.Util import * +import TestCmd class UtilTestCase(unittest.TestCase): def test_str2nodes(self): @@ -159,6 +159,21 @@ class UtilTestCase(unittest.TestCase): assert len(cmd_list) == 2, cmd_list assert cmd_list[1][0] == 'after', cmd_list[1][0] assert cmd_list[0][2] == cvt('../foo/ack.cbefore'), cmd_list[0][2] + + def test_find_files(self): + """Testing find_files function.""" + test = TestCmd.TestCmd(workdir = '') + test.write('./foo', 'Some file\n') + fs = SCons.Node.FS.FS(test.workpath("")) + node_derived = fs.File(test.workpath('./bar/baz')) + node_derived.builder_set(1) # Any non-zero value. + nodes = find_files(['foo', 'baz'], + map(test.workpath, ['./', './bar' ]), fs.File) + file_names = map(str, nodes) + file_names = map(os.path.normpath, file_names) + assert os.path.normpath('./foo') in file_names, file_names + assert os.path.normpath('./bar/baz') in file_names, file_names + if __name__ == "__main__": suite = unittest.makeSuite(UtilTestCase, 'test_') diff --git a/test/CPPPATH.py b/test/CPPPATH.py index dec523d..32c00b6 100644 --- a/test/CPPPATH.py +++ b/test/CPPPATH.py @@ -28,11 +28,44 @@ import TestSCons test = TestSCons.TestSCons() -test.pass_test() #XXX Short-circuit until this is implemented. +test.write('foo.c', +"""#include "include/foo.h" +#include + +int main(void) +{ + printf(TEST_STRING); + return 0; +} +""") + +test.subdir('include') + +test.write('include/foo.h', +""" +#define TEST_STRING "Bad news\n" +""") test.write('SConstruct', """ +env = Environment() +env.Program(target='prog', source='foo.c') +#env.Depends(target='foo.c', dependency='include/foo.h') """) -test.run(arguments = '.') +test.run(arguments = 'prog') + +test.run(program = test.workpath('prog'), + stdout = "Bad news\n") + +test.unlink('include/foo.h') +test.write('include/foo.h', +""" +#define TEST_STRING "Good news\n" +""") + +test.run(arguments = 'prog') + +test.run(program = test.workpath('prog'), + stdout = "Good news\n") test.pass_test() diff --git a/test/LIBPATH.py b/test/LIBPATH.py index dec523d..2efd236 100644 --- a/test/LIBPATH.py +++ b/test/LIBPATH.py @@ -28,11 +28,36 @@ import TestSCons test = TestSCons.TestSCons() -test.pass_test() #XXX Short-circuit until this is implemented. - test.write('SConstruct', """ +env = Environment(LIBS = [ 'foo1' ], + LIBPATH = [ './libs' ]) +env.Program(target = 'prog', source = 'prog.c') +env.Library(target = './libs/foo1', source = 'f1.c') +""") + +test.write('f1.c', """ +void +f1(void) +{ + printf("f1.c\n"); +} """) -test.run(arguments = '.') +test.write('prog.c', """ +void f1(void); +int +main(int argc, char *argv[]) +{ + argv[argc++] = "--"; + f1(); + printf("prog.c\n"); + return 0; +} +""") + +test.run(arguments = 'prog') + +test.run(program = test.workpath('prog'), + stdout = "f1.c\nprog.c\n") test.pass_test() diff --git a/test/Library.py b/test/Library.py index 3c50f3d..f5e9edb 100644 --- a/test/Library.py +++ b/test/Library.py @@ -28,16 +28,13 @@ import TestSCons test = TestSCons.TestSCons() -#XXX Need to switch TestBld to Program() when LIBS variable is working. test.write('SConstruct', """ -TestBld = Builder(name='TestBld', - action='cc -o $TARGET $SOURCES -L./ -lfoo1 -lfoo2 -lfoo3') -env = Environment(BUILDERS=[ TestBld, Library ]) +env = Environment(LIBS = [ 'foo1', 'foo2', 'foo3' ], + LIBPATH = [ './' ]) env.Library(target = 'foo1', source = 'f1.c') env.Library(target = 'foo2', source = 'f2a.c f2b.c f2c.c') env.Library(target = 'foo3', source = ['f3a.c', 'f3b.c', 'f3c.c']) -env.TestBld(target = 'prog', source = 'prog.c') -env.Depends(target = 'prog', dependency = 'libfoo1.a libfoo2.a libfoo3.a') +env.Program(target = 'prog', source = 'prog.c') """) test.write('f1.c', """ @@ -119,7 +116,7 @@ main(int argc, char *argv[]) } """) -test.run(arguments = 'libfoo1.a libfoo2.a libfoo3.a prog') +test.run(arguments = 'prog') test.run(program = test.workpath('prog'), stdout = "f1.c\nf2a.c\nf2b.c\nf2c.c\nf3a.c\nf3b.c\nf3c.c\nprog.c\n") -- cgit v0.12