From 7b142dbd32c3673ae57bda1fea98a8d8e3f7d0e5 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Mon, 29 Jul 2002 23:22:50 +0000 Subject: Multiple directory .h includes in Repositories. --- src/engine/SCons/Action.py | 13 ++- src/engine/SCons/ActionTests.py | 4 +- src/engine/SCons/Node/FS.py | 85 +++++++++++++++--- src/engine/SCons/Node/FSTests.py | 59 ++++++++++++- src/engine/SCons/Node/__init__.py | 3 + src/engine/SCons/Scanner/C.py | 4 +- src/engine/SCons/Scanner/CTests.py | 35 +++++++- src/engine/SCons/Scanner/Fortran.py | 4 +- src/engine/SCons/Scanner/FortranTests.py | 25 ++++++ src/engine/SCons/Script/SConscript.py | 10 +-- src/engine/SCons/Script/__init__.py | 2 +- src/engine/SCons/Sig/SigTests.py | 3 + test/Repository/multi-dir.py | 146 +++++++++++++++++++++++++++++++ 13 files changed, 358 insertions(+), 35 deletions(-) create mode 100644 test/Repository/multi-dir.py diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py index 79fdd2c..1ddecdf 100644 --- a/src/engine/SCons/Action.py +++ b/src/engine/SCons/Action.py @@ -271,12 +271,6 @@ class ActionBase: else: del kw['dir'] - def rstr(x): - try: - return x.rstr() - except AttributeError: - return str(x) - if kw.has_key('target'): t = kw['target'] del kw['target'] @@ -286,11 +280,16 @@ class ActionBase: cwd = t[0].cwd except (IndexError, AttributeError): pass - dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(rstr, t))) + dict['TARGETS'] = SCons.Util.PathList(map(os.path.normpath, map(str, t))) if dict['TARGETS']: dict['TARGET'] = dict['TARGETS'][0] if kw.has_key('source'): + def rstr(x): + try: + return x.rstr() + except AttributeError: + return str(x) s = kw['source'] del kw['source'] if not SCons.Util.is_List(s): diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py index 5fa1b8b..16bcd80 100644 --- a/src/engine/SCons/ActionTests.py +++ b/src/engine/SCons/ActionTests.py @@ -117,13 +117,15 @@ class ActionBaseTestCase(unittest.TestCase): class N: def __init__(self, name): self.name = name + def __str__(self): + return self.name def rstr(self): return 'rstr-' + self.name d = a.subst_dict(target = [N('t3'), 't4'], source = ['s3', N('s4')]) TARGETS = map(lambda x: str(x), d['TARGETS']) TARGETS.sort() - assert TARGETS == ['rstr-t3', 't4'], d['TARGETS'] + assert TARGETS == ['t3', 't4'], d['TARGETS'] SOURCES = map(lambda x: str(x), d['SOURCES']) SOURCES.sort() assert SOURCES == ['rstr-s4', 's3'], d['SOURCES'] diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 0837ed3..dfea21b 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -307,12 +307,12 @@ class FS: build_dir.link(src_dir, duplicate) def Repository(self, *dirs): - """Specify repository directories to search.""" + """Specify Repository directories to search.""" for d in dirs: self.Repositories.append(self.Dir(d)) def Rsearch(self, path, func = exists_path): - """Search for something in a repository. Returns the first + """Search for something in a Repository. Returns the first one found in the list, or None if there isn't one.""" if isinstance(path, SCons.Node.Node): return path @@ -327,10 +327,12 @@ class FS: return None def Rsearchall(self, pathlist, func = exists_path): - """Search for a list of somethings in the repository list.""" + """Search for a list of somethings in the Repository list.""" ret = [] if SCons.Util.is_String(pathlist): pathlist = string.split(pathlist, os.pathsep) + if not SCons.Util.is_List(pathlist): + pathlist = [pathlist] for path in pathlist: if isinstance(path, SCons.Node.Node): ret.append(path) @@ -339,6 +341,8 @@ class FS: if n: ret.append(n) if not os.path.isabs(path): + if path[0] == '#': + path = path[1:] for dir in self.Repositories: n = func(os.path.join(dir.path, path)) if n: @@ -422,7 +426,7 @@ class Entry(SCons.Node.Node): raise AttributeError def exists(self): - return os.path.exists(self.rstr()) + return os.path.exists(self.path) def cached_exists(self): try: @@ -431,6 +435,9 @@ class Entry(SCons.Node.Node): self.exists_flag = self.exists() return self.exists_flag + def rexists(self): + return os.path.exists(self.rstr()) + def get_parents(self): parents = SCons.Node.Node.get_parents(self) if self.dir and not isinstance(self.dir, ParentOfRoot): @@ -457,7 +464,6 @@ class Entry(SCons.Node.Node): # XXX TODO? # Annotate with the creator -# is_under # rel_path # srcpath / srcdir # link / is_linked @@ -588,12 +594,14 @@ class Dir(Entry): # source path exists, we only care about the path. return os.path.exists(self.path) + def rexists(self): + # Again, directories are special...we don't care if their + # source path exists, we only care about the path. + return os.path.exists(self.rstr()) + # XXX TODO? -# rfile -# precious -# rpath # rsrcpath # source_exists # derived_exists @@ -604,9 +612,7 @@ class Dir(Entry): # addsuffix # accessible # ignore -# build # bind -# is_under # relpath class File(Entry): @@ -624,16 +630,44 @@ class File(Entry): return self.dir.root() def get_contents(self): - if not self.exists(): + if not self.rexists(): return '' return open(self.rstr(), "rb").read() def get_timestamp(self): - if self.exists(): + if self.rexists(): return os.path.getmtime(self.rstr()) else: return 0 + def calc_signature(self, calc): + """ + Select and calculate the appropriate build signature for a File. + + self - the File node + calc - the signature calculation module + returns - the signature + + This method does not store the signature in the node or + in the .sconsign file. + """ + + if self.builder: + if SCons.Sig.build_signature: + if not hasattr(self, 'bsig'): + self.set_bsig(calc.bsig(self.rfile())) + return self.get_bsig() + else: + if not hasattr(self, 'csig'): + self.set_csig(calc.csig(self.rfile())) + return self.get_csig() + elif not self.rexists(): + return None + else: + if not hasattr(self, 'csig'): + self.set_csig(calc.csig(self.rfile())) + return self.get_csig() + def store_csig(self): self.dir.sconsign().set_csig(self.name, self.get_csig()) @@ -671,6 +705,17 @@ class File(Entry): file_link(self.srcpath, self.path) return Entry.exists(self) + def rexists(self): + if self.duplicate and not self.created: + self.created = 1 + if self.srcpath != self.path and \ + os.path.exists(self.srcpath): + if os.path.exists(self.path): + os.unlink(self.path) + self.__createDir() + file_link(self.srcpath, self.path) + return Entry.rexists(self) + def scanner_key(self): return os.path.splitext(self.name)[1] @@ -705,6 +750,22 @@ class File(Entry): else: self.__createDir() + def current(self, calc): + bsig = calc.bsig(self) + if not self.exists(): + # The file doesn't exist locally... + r = self.rfile() + if r != self: + # ...but there is one in a Repository... + if calc.current(r, bsig): + # ...and it's even up-to-date. + # XXX Future: copy locally if requested + return 1 + self._rfile = self + return None + else: + return calc.current(self, bsig) + def rfile(self): if not hasattr(self, '_rfile'): self._rfile = self diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py index 42a6542..b45cc4b 100644 --- a/src/engine/SCons/Node/FSTests.py +++ b/src/engine/SCons/Node/FSTests.py @@ -27,6 +27,7 @@ import os import os.path import string import sys +import time import unittest import SCons.Node.FS from TestCmd import TestCmd @@ -501,7 +502,18 @@ class FSTestCase(unittest.TestCase): assert c == "", c assert e.__class__ == SCons.Node.FS.Dir - #XXX test get_timestamp() + test.write("tstamp", "tstamp\n") + # Okay, *this* manipulation accomodates Windows FAT file systems + # that only have two-second granularity on their timestamps. + # We round down the current time to the nearest even integer + # value, subtract two to make sure the timestamp is not "now," + # and then convert it back to a float. + tstamp = float(int(time.time() / 2) * 2) - 2 + os.utime(test.workpath("tstamp"), (tstamp - 2.0, tstamp)) + f = fs.File("tstamp") + t = f.get_timestamp() + assert t == tstamp, "expected %f, got %f" % (tstamp, t) + test.unlink("tstamp") #XXX test get_prevsiginfo() @@ -530,6 +542,10 @@ class FSTestCase(unittest.TestCase): exc_caught = 1 assert exc_caught, "Should have caught a TypeError" + # XXX test calc_signature() + + # XXX test current() + class RepositoryTestCase(unittest.TestCase): def runTest(self): """Test FS (file system) Repository operations @@ -585,6 +601,10 @@ class RepositoryTestCase(unittest.TestCase): assert fs.Rsearch('f2', os.path.exists) assert fs.Rsearch('f3', os.path.exists) + list = fs.Rsearchall(fs.Dir('d1')) + assert len(list) == 1, list + assert list[0].path == 'd1', list[0].path + list = fs.Rsearchall([fs.Dir('d1')]) assert len(list) == 1, list assert list[0].path == 'd1', list[0].path @@ -592,6 +612,9 @@ class RepositoryTestCase(unittest.TestCase): list = fs.Rsearchall('d2') assert list == [], list + list = fs.Rsearchall('#d2') + assert list == [], list + test.subdir(['work', 'd2']) list = fs.Rsearchall('d2') assert list == ['d2'], list @@ -623,6 +646,40 @@ class RepositoryTestCase(unittest.TestCase): work_d4 = fs.File(os.path.join('work', 'd4')) list = fs.Rsearchall(['d3', work_d4]) assert list == ['d3', work_d4], list + + f1 = fs.File(test.workpath("work", "i_do_not_exist")) + assert not f1.rexists() + + test.write(["rep2", "i_exist"], "\n") + f1 = fs.File(test.workpath("work", "i_exist")) + assert f1.rexists() + + test.write(["work", "i_exist_too"], "\n") + f1 = fs.File(test.workpath("work", "i_exist_too")) + assert f1.rexists() + + test.write(["rep2", "tstamp"], "tstamp\n") + # Okay, *this* manipulation accomodates Windows FAT file systems + # that only have two-second granularity on their timestamps. + # We round down the current time to the nearest even integer + # value, subtract two to make sure the timestamp is not "now," + # and then convert it back to a float. + tstamp = float(int(time.time() / 2) * 2) - 2 + os.utime(test.workpath("rep2", "tstamp"), (tstamp - 2.0, tstamp)) + f = fs.File("tstamp") + t = f.get_timestamp() + assert t == tstamp, "expected %f, got %f" % (tstamp, t) + test.unlink(["rep2", "tstamp"]) + + # Make sure get_contents() returns the binary contents. + test.write(["rep3", "contents"], "Con\x1aTents\n") + c = fs.File("contents").get_contents() + assert c == "Con\x1aTents\n", "got '%s'" % c + test.unlink(["rep3", "contents"]) + + # XXX test calc_signature() + + # XXX test current() class find_fileTestCase(unittest.TestCase): def runTest(self): diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 45f5bc7..a65310d 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -395,6 +395,9 @@ class Node: def current(self): return None + def rfile(self): + return self + def rstr(self): return str(self) diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index d37bda8..f388b63 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -81,14 +81,14 @@ def scan(node, env, target, fs = SCons.Node.FS.default_fs): if not hasattr(target, 'cpppath'): def Dir(x, dir=target.cwd, fs=fs): return fs.Dir(x,dir) try: - target.cpppath = tuple(SCons.Node.arg2nodes(env['CPPPATH'],Dir)) + target.cpppath = tuple(fs.Rsearchall(env['CPPPATH'], Dir)) except KeyError: target.cpppath = () cpppath = target.cpppath if not node.found_includes.has_key(cpppath): - if node.exists(): + if node.rexists(): # cache the includes list in node so we only scan it once: if node.includes != None: diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index faa4cdd..8a70134 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -113,10 +113,25 @@ int main() } """) -test.write('include/fa.h', "\n") -test.write('include/fb.h', "\n") -test.write('subdir/include/fa.h', "\n") -test.write('subdir/include/fb.h', "\n") +test.write(['include', 'fa.h'], "\n") +test.write(['include', 'fb.h'], "\n") +test.write(['subdir', 'include', 'fa.h'], "\n") +test.write(['subdir', 'include', 'fb.h'], "\n") + + +test.subdir('repository', ['repository', 'include']) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.h'], "\n") + +test.write(['work', 'src', 'fff.c'], """ +#include + +int main() +{ + return 0; +} +""") # define some helpers: @@ -267,6 +282,17 @@ class CScannerTestCase10(unittest.TestCase): deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ]) test.unlink('include/fa.cpp') +class CScannerTestCase11(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + s = SCons.Scanner.C.CScan(fs=fs) + env = DummyEnvironment(['include']) + deps = s.scan(fs.File('src/fff.c'), env, DummyTarget()) + deps_match(self, deps, [test.workpath('repository/include/iii.h')]) + os.chdir(test.workpath('')) + def suite(): suite = unittest.TestSuite() suite.addTest(CScannerTestCase1()) @@ -278,6 +304,7 @@ def suite(): suite.addTest(CScannerTestCase8()) suite.addTest(CScannerTestCase9()) suite.addTest(CScannerTestCase10()) + suite.addTest(CScannerTestCase11()) return suite if __name__ == "__main__": diff --git a/src/engine/SCons/Scanner/Fortran.py b/src/engine/SCons/Scanner/Fortran.py index 17c9241..e87b885 100644 --- a/src/engine/SCons/Scanner/Fortran.py +++ b/src/engine/SCons/Scanner/Fortran.py @@ -79,7 +79,7 @@ def scan(node, env, target, fs = SCons.Node.FS.default_fs): if not hasattr(target, 'f77path'): def Dir(x, dir=target.cwd, fs=fs): return fs.Dir(x,dir) try: - target.f77path = tuple(SCons.Node.arg2nodes(env['F77PATH'],Dir)) + target.f77path = tuple(fs.Rsearchall(env['F77PATH'], Dir)) except KeyError: target.f77path = () @@ -90,7 +90,7 @@ def scan(node, env, target, fs = SCons.Node.FS.default_fs): try: nodes = node.found_includes[f77path] except KeyError: - if node.exists(): + if node.rexists(): # cache the includes list in node so we only scan it once: if node.includes != None: diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py index 2883daf..6d2a066 100644 --- a/src/engine/SCons/Scanner/FortranTests.py +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -92,6 +92,19 @@ test.write('fff4.f',""" test.write('include/f4.f', "\n") test.write('subdir/include/f4.f', "\n") + +test.subdir('repository', ['repository', 'include']) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.f'], "\n") + +test.write(['work', 'src', 'fff.f'], """ + PROGRAM FOO + INCLUDE 'iii.f' + STOP + END +""") + # define some helpers: class DummyTarget: @@ -278,6 +291,17 @@ class FortranScannerTestCase12(unittest.TestCase): deps_match(self, deps, ['include/f4.f']) test.unlink('include/fff4.f') +class FortranScannerTestCase13(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + s = SCons.Scanner.Fortran.FortranScan(fs=fs) + env = DummyEnvironment(['include']) + deps = s.scan(fs.File('src/fff.f'), env, DummyTarget()) + deps_match(self, deps, [test.workpath('repository/include/iii.f')]) + os.chdir(test.workpath('')) + def suite(): suite = unittest.TestSuite() suite.addTest(FortranScannerTestCase1()) @@ -292,6 +316,7 @@ def suite(): suite.addTest(FortranScannerTestCase10()) suite.addTest(FortranScannerTestCase11()) suite.addTest(FortranScannerTestCase12()) + suite.addTest(FortranScannerTestCase13()) return suite if __name__ == "__main__": diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index e088c99..38fd080 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -162,11 +162,11 @@ def SConscript(*ls, **kw): if fn == "-": exec sys.stdin in stack[-1].globals else: - if isinstance(fn, SCons.Node.Node): - f = fn - else: - f = SCons.Node.FS.default_fs.File(str(fn)) - if f.exists(): + if isinstance(fn, SCons.Node.Node): + f = fn + else: + f = SCons.Node.FS.default_fs.File(str(fn)) + if f.rexists(): file = open(f.rstr(), "r") SCons.Node.FS.default_fs.chdir(f.dir) if sconscript_chdir: diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index 10794f5..17a1256 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -835,7 +835,7 @@ def _SConstruct_exists(dirname=''): sfile = os.path.join(dirname, file) if os.path.isfile(sfile): return sfile - if not os.path.isabs(file): + if not os.path.isabs(sfile): for rep in repositories: if os.path.isfile(os.path.join(rep, sfile)): return sfile diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index 8c9125a..37413aa 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -81,6 +81,9 @@ class DummyNode: self.exists_cache = self.exists() return self.exists_cache + def rexists(self): + return not self.file.contents is None + def children(self): return filter(lambda x, i=self.ignore: x not in i, self.sources + self.depends) diff --git a/test/Repository/multi-dir.py b/test/Repository/multi-dir.py new file mode 100644 index 0000000..a588814 --- /dev/null +++ b/test/Repository/multi-dir.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002 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 sys +import TestSCons + +if sys.platform == 'win32': + _exe = '.exe' +else: + _exe = '' + + + +test = TestSCons.TestSCons() + +# +test.subdir('work', + ['work', 'src'], + ['work', 'include'], + 'repository', + ['repository', 'src'], + ['repository', 'include']) + +# +workpath_repository = test.workpath('repository') +work_include_my_string_h = test.workpath('work', 'include', 'my_string.h') +work_src_xxx = test.workpath('work', 'src', 'xxx') +repository_src_xxx = test.workpath('repository', 'src', 'xxx') + +opts = "-Y " + workpath_repository + +# +test.write(['repository', 'SConstruct'], """ +env = Environment(CPPPATH = ['#src', '#include']) +SConscript('src/SConscript', "env") +""") + +test.write(['repository', 'src', 'SConscript'], """ +Import("env") +env.Program(target = 'xxx', source = 'main.c') +""") + +test.write(['repository', 'include', 'my_string.h'], r""" +#define MY_STRING "repository/include/my_string.h" +""") + +test.write(['repository', 'src', 'include.h'], r""" +#include +#define LOCAL_STRING "repository/src/include.h" +""") + +test.write(['repository', 'src', 'main.c'], r""" +#include +int +main(int argc, char *argv[]) +{ + argv[argc++] = "--"; + printf("%s\n", MY_STRING); + printf("%s\n", LOCAL_STRING); + printf("repository/src/main.c\n"); + exit (0); +} +""") + +# +test.run(chdir = 'repository', arguments = ".") + +test.run(program = repository_src_xxx, stdout = +"""repository/include/my_string.h +repository/src/include.h +repository/src/main.c +""") + +# Double-check that the Repository is up-to-date. +#test.up_to_date(chdir = 'repository', arguments = ".") + +# Make the repository non-writable, +# so we'll detect if we try to write into it accidentally. +test.writable('repository', 0) + +# Because the Repository is completely up-to-date, +# a build in an empty work directory should also be up-to-date. +test.up_to_date(chdir = 'work', options = opts, arguments = ".") + +test.write(['work', 'include', 'my_string.h'], r""" +#define MY_STRING "work/include/my_string.h" +""") + +test.run(chdir = 'work', options = opts, arguments = ".") + +test.run(program = work_src_xxx, stdout = +"""work/include/my_string.h +repository/src/include.h +repository/src/main.c +""") + +test.write(['work', 'src', 'include.h'], r""" +#include +#define LOCAL_STRING "work/src/include.h" +""") + +test.run(chdir = 'work', options = opts, arguments = ".") + +test.run(program = work_src_xxx, stdout = +"""work/include/my_string.h +work/src/include.h +repository/src/main.c +""") + +# +test.unlink(work_include_my_string_h) + +test.run(chdir = 'work', options = opts, arguments = ".") + +test.run(program = work_src_xxx, stdout = +"""repository/include/my_string.h +work/src/include.h +repository/src/main.c +""") + +# +test.pass_test() +__END__ -- cgit v0.12