From cecc2aac039236424389b9a202ab95fb1cc6eeb7 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Sat, 25 Sep 2004 02:50:45 +0000 Subject: [PATCH] Add a ParseDepends() function to read up mkdep-style files. git-svn-id: http://scons.tigris.org/svn/scons/trunk@1101 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- doc/man/scons.1 | 35 ++++++ src/CHANGES.txt | 3 + src/engine/SCons/Environment.py | 26 +++++ src/engine/SCons/EnvironmentTests.py | 32 ++++++ src/engine/SCons/Script/SConscript.py | 1 + src/engine/SCons/Util.py | 29 +++++ src/engine/SCons/UtilTests.py | 23 ++++ test/ParseDepends.py | 157 ++++++++++++++++++++++++++ 8 files changed, 306 insertions(+) create mode 100644 test/ParseDepends.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 5cad3f2a..dda0ce26 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -3189,6 +3189,41 @@ and added to the .B LIBS construction variable. +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI ParseDepends( filename ", [" must_exist ]) +.TP +.RI env.ParseDepends( filename ", [" must_exist ]) +Parses the contents of the specified +.I filename +as a list of dependencies in the style of +.BR Make +or +.BR mkdep , +and explicitly establishes all of the listed dependencies. +By default, +it is not an error +if the specified +.I filename +does not exist. +The optional +.I must_exit +argument may be set to a non-zero +value to have +scons +throw an exception and +generate an error if the file does not exist, +or is otherwise inaccessible. +The +.I filename +and all of the files listed therein +will be interpreted relative to +the directory of the +.I SConscript +file which called the +.B ParseDepends +function. + '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP env.Perforce() diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 1008e8ee..7379368a 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -69,6 +69,9 @@ RELEASE 0.97 - XXX - Allow a ListOption's default value(s) to be a Python list of specified values, not just a string containing a comma-separated list of names. + - Add a ParseDepends() function that will parse up a list of explicit + dependencies from a "make depend" style file. + From Clive Levinson: - Make ParseConfig() recognize and add -mno-cygwin to $LINKFLAGS and diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index df2a93a6..9fa7b341 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -792,6 +792,32 @@ class Base: command = self.subst(command) return function(self, os.popen(command).read()) + def ParseDepends(self, filename, must_exist=None): + """ + Parse a mkdep-style file for explicit dependencies. This is + completely abusable, and should be unnecessary in the "normal" + case of proper SCons configuration, but it may help make + the transition from a Make hierarchy easier for some people + to swallow. It can also be genuinely useful when using a tool + that can write a .d file, but for which writing a scanner would + be too complicated. + """ + try: + fp = open(filename, 'r') + except IOError: + if must_exist: + raise + return + for line in SCons.Util.LogicalLines(fp).readlines(): + if line[0] == '#': + continue + try: + target, depends = string.split(line, ':', 1) + except: + pass + else: + self.Depends(string.split(target), string.split(depends)) + def Platform(self, platform): platform = self.subst(platform) return SCons.Platform.Platform(platform)(self) diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 7023fea5..aa38965a 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -1353,6 +1353,38 @@ class EnvironmentTestCase(unittest.TestCase): finally: os.popen = orig_popen + def test_ParseDepends(self): + """Test the ParseDepends() method""" + env = Environment() + + test = TestCmd.TestCmd(workdir = '') + + test.write('mkdep', """ +f1: foo +f2 f3: bar +f4: abc def +#file: dependency +f5: \ + ghi \ + jkl \ + mno \ +""") + + tlist = [] + dlist = [] + def my_depends(target, dependency, tlist=tlist, dlist=dlist): + tlist.extend(target) + dlist.extend(dependency) + + env.Depends = my_depends + + env.ParseDepends(test.workpath('mkdep')) + + t = map(str, tlist) + d = map(str, dlist) + assert t == ['f1', 'f2', 'f3', 'f4', 'f5'], t + assert d == ['foo', 'bar', 'abc', 'def', 'ghi', 'jkl', 'mno'], d + def test_Platform(self): """Test the Platform() method""" env = Environment(WIN32='win32', NONE='no-such-platform') diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index fab0d82a..88d32684 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -594,6 +594,7 @@ GlobalDefaultEnvironmentFunctions = [ 'InstallAs', 'Literal', 'Local', + 'ParseDepends', 'Precious', 'Repository', 'SConsignFile', diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index df2f6040..afcaf112 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -1520,3 +1520,32 @@ def unique(s): if x not in u: u.append(x) return u + +# Much of the logic here was originally based on recipe 4.9 from the +# Python CookBook, but we had to dumb it way down for Python 1.5.2. +class LogicalLines: + + def __init__(self, fileobj): + self.fileobj = fileobj + + def readline(self): + result = [] + while 1: + line = self.fileobj.readline() + if not line: + break + if line[-2:] == '\\\n': + result.append(line[:-2]) + else: + result.append(line) + break + return string.join(result, '') + + def readlines(self): + result = [] + while 1: + line = self.readline() + if not line: + break + result.append(line) + return result diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py index 2b5fdefd..054d0b24 100644 --- a/src/engine/SCons/UtilTests.py +++ b/src/engine/SCons/UtilTests.py @@ -1552,6 +1552,29 @@ class UtilTestCase(unittest.TestCase): assert containsOnly('.83', '0123456789.') assert not containsOnly('43221', '123') + def test_LogicalLines(self): + """Test the LogicalLines class""" + import StringIO + + fobj = StringIO.StringIO(r""" +foo \ +bar \ +baz +foo +bling \ +bling \ bling +bling +""") + + lines = LogicalLines(fobj).readlines() + assert lines == [ + '\n', + 'foo bar baz\n', + 'foo\n', + 'bling bling \\ bling\n', + 'bling\n', + ], lines + if __name__ == "__main__": suite = unittest.makeSuite(UtilTestCase, 'test_') if not unittest.TextTestRunner().run(suite).wasSuccessful(): diff --git a/test/ParseDepends.py b/test/ParseDepends.py new file mode 100644 index 00000000..9a9910ab --- /dev/null +++ b/test/ParseDepends.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os.path +import string + +import TestSCons + +python = TestSCons.python + +test = TestSCons.TestSCons() + +test.subdir('subdir', 'sub2') + +test.write('build.py', r""" +import sys +contents = open(sys.argv[2], 'rb').read() + open(sys.argv[3], 'rb').read() +file = open(sys.argv[1], 'wb') +file.write(contents) +file.close() +""") + +test.write('SConstruct', """ +Foo = Builder(action = r"%s build.py $TARGET $SOURCES subdir/foo.dep") +Bar = Builder(action = r"%s build.py $TARGET $SOURCES subdir/bar.dep") +env = Environment(BUILDERS = { 'Foo' : Foo, 'Bar' : Bar }, SUBDIR='subdir') +env.ParseDepends('foo.d') +env.ParseDepends('bar.d') +env.Foo(target = 'f1.out', source = 'f1.in') +env.Foo(target = 'f2.out', source = 'f2.in') +env.Bar(target = 'subdir/f3.out', source = 'f3.in') +SConscript('subdir/SConscript', "env") +env.Foo(target = 'f5.out', source = 'f5.in') +env.Bar(target = 'sub2/f6.out', source = 'f6.in') +""" % (python, python)) + +test.write('foo.d', "f1.out f2.out: %s\n" % os.path.join('subdir', 'foo.dep')) +test.write('bar.d', "%s: %s\nf5.out: sub2" % (os.path.join('subdir', 'f3.out'), + os.path.join('subdir', 'bar.dep'))) + +test.write(['subdir', 'SConscript'], """ +Import("env") +ParseDepends('bar.d') +env.Bar(target = 'f4.out', source = 'f4.in') +""") + +test.write(['subdir', 'bar.d'], "f4.out: bar.dep\n") + +test.write('f1.in', "f1.in\n") +test.write('f2.in', "f2.in\n") +test.write('f3.in', "f3.in\n") +test.write(['subdir', 'f4.in'], "subdir/f4.in\n") +test.write('f5.in', "f5.in\n") +test.write('f6.in', "f6.in\n") + +test.write(['subdir', 'foo.dep'], "subdir/foo.dep 1\n") +test.write(['subdir', 'bar.dep'], "subdir/bar.dep 1\n") + +test.run(arguments = '.') + +test.must_match('f1.out', "f1.in\nsubdir/foo.dep 1\n") +test.must_match('f2.out', "f2.in\nsubdir/foo.dep 1\n") +test.must_match(['subdir', 'f3.out'], "f3.in\nsubdir/bar.dep 1\n") +test.must_match(['subdir', 'f4.out'], "subdir/f4.in\nsubdir/bar.dep 1\n") +test.must_match('f5.out', "f5.in\nsubdir/foo.dep 1\n") +test.must_match(['sub2', 'f6.out'], "f6.in\nsubdir/bar.dep 1\n") + +# +test.write(['subdir', 'foo.dep'], "subdir/foo.dep 2\n") +test.write(['subdir', 'bar.dep'], "subdir/bar.dep 2\n") +test.write('f6.in', "f6.in 2\n") + +test.run(arguments = '.') + +test.must_match('f1.out', "f1.in\nsubdir/foo.dep 2\n") +test.must_match('f2.out', "f2.in\nsubdir/foo.dep 2\n") +test.must_match(['subdir', 'f3.out'], "f3.in\nsubdir/bar.dep 2\n") +test.must_match(['subdir', 'f4.out'], "subdir/f4.in\nsubdir/bar.dep 2\n") +test.must_match('f5.out', "f5.in\nsubdir/foo.dep 2\n") +test.must_match(['sub2', 'f6.out'], "f6.in 2\nsubdir/bar.dep 2\n") + +# +test.write(['subdir', 'foo.dep'], "subdir/foo.dep 3\n") + +test.run(arguments = '.') + +test.must_match('f1.out', "f1.in\nsubdir/foo.dep 3\n") +test.must_match('f2.out', "f2.in\nsubdir/foo.dep 3\n") +test.must_match(['subdir', 'f3.out'], "f3.in\nsubdir/bar.dep 2\n") +test.must_match(['subdir', 'f4.out'], "subdir/f4.in\nsubdir/bar.dep 2\n") +test.must_match('f5.out', "f5.in\nsubdir/foo.dep 2\n") +test.must_match(['sub2', 'f6.out'], "f6.in 2\nsubdir/bar.dep 2\n") + +# +test.write(['subdir', 'bar.dep'], "subdir/bar.dep 3\n") + +test.run(arguments = '.') + +test.must_match('f1.out', "f1.in\nsubdir/foo.dep 3\n") +test.must_match('f2.out', "f2.in\nsubdir/foo.dep 3\n") +test.must_match(['subdir', 'f3.out'], "f3.in\nsubdir/bar.dep 3\n") +test.must_match(['subdir', 'f4.out'], "subdir/f4.in\nsubdir/bar.dep 3\n") +test.must_match('f5.out', "f5.in\nsubdir/foo.dep 2\n") +test.must_match(['sub2', 'f6.out'], "f6.in 2\nsubdir/bar.dep 2\n") + +# +test.write('f6.in', "f6.in 3\n") + +test.run(arguments = '.') + +test.must_match('f1.out', "f1.in\nsubdir/foo.dep 3\n") +test.must_match('f2.out', "f2.in\nsubdir/foo.dep 3\n") +test.must_match(['subdir', 'f3.out'], "f3.in\nsubdir/bar.dep 3\n") +test.must_match(['subdir', 'f4.out'], "subdir/f4.in\nsubdir/bar.dep 3\n") +test.must_match('f5.out', "f5.in\nsubdir/foo.dep 3\n") +test.must_match(['sub2', 'f6.out'], "f6.in 3\nsubdir/bar.dep 3\n") + + + +test.write('SConstruct', """ +ParseDepends('nonexistent_file') +""") + +test.run() + +test.write('SConstruct', """ +ParseDepends('nonexistent_file', must_exist=1) +""") + +test.run(status=2, stderr=None) + +test.fail_test(string.find(test.stderr(), "No such file or directory") == -1) + +test.pass_test() -- 2.26.2