From b155ebc4a869e0669ee2f7124531e0b5950c7528 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Thu, 6 May 2004 06:21:43 +0000 Subject: Refactor .sconsign management into its own module. --- src/engine/MANIFEST.in | 1 + src/engine/SCons/Environment.py | 3 +- src/engine/SCons/EnvironmentTests.py | 8 +- src/engine/SCons/Node/FS.py | 4 +- src/engine/SCons/Node/__init__.py | 4 +- src/engine/SCons/SConfTests.py | 4 +- src/engine/SCons/SConsign.py | 323 +++++++++++++++++++++++++++++++++++ src/engine/SCons/SConsignTests.py | 180 +++++++++++++++++++ src/engine/SCons/Script/__init__.py | 2 +- src/engine/SCons/Sig/SigTests.py | 138 --------------- src/engine/SCons/Sig/__init__.py | 290 +------------------------------ src/script/sconsign.py | 4 +- 12 files changed, 521 insertions(+), 440 deletions(-) create mode 100644 src/engine/SCons/SConsign.py create mode 100644 src/engine/SCons/SConsignTests.py diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 4942be8..1b8a06f 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -40,6 +40,7 @@ SCons/Scanner/Fortran.py SCons/Scanner/IDL.py SCons/Scanner/Prog.py SCons/SConf.py +SCons/SConsign.py SCons/Script/SConscript.py SCons/Script/__init__.py SCons/Sig/__init__.py diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index d4279d1..b920643 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -53,6 +53,7 @@ import SCons.Node.Alias import SCons.Node.FS import SCons.Node.Python import SCons.Platform +import SCons.SConsign import SCons.Sig import SCons.Sig.MD5 import SCons.Sig.TimeStamp @@ -1164,7 +1165,7 @@ class Base: name = self.subst(name) if not os.path.isabs(name): name = os.path.join(str(self.fs.SConstruct_dir), name) - SCons.Sig.SConsignFile(name, dbm_module) + SCons.SConsign.File(name, dbm_module) def SideEffect(self, side_effect, target): """Tell scons that side_effects are built as side diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 8bab246..8ac43cb 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -2214,7 +2214,7 @@ class EnvironmentTestCase(unittest.TestCase): def test_SConsignFile(self): """Test the SConsignFile() method""" - import SCons.Sig + import SCons.SConsign class MyFS: SConstruct_dir = os.sep + 'dir' @@ -2230,8 +2230,8 @@ class EnvironmentTestCase(unittest.TestCase): fnames.append(name) dbms.append(dbm_module) - save_Sig_SConsignFile = SCons.Sig.SConsignFile - SCons.Sig.SConsignFile = capture + save_SConsign_File = SCons.SConsign.File + SCons.SConsign.File = capture env.SConsignFile('foo') assert fnames[0] == os.path.join(os.sep, 'dir', 'foo'), fnames @@ -2257,7 +2257,7 @@ class EnvironmentTestCase(unittest.TestCase): assert fnames[5] == os.path.join(os.sep, 'dir', '.sconsign'), fnames assert dbms[5] == None, dbms finally: - SCons.Sig.SConsignFile = save_Sig_SConsignFile + SCons.SConsign.File = save_SConsign_File def test_SideEffect(self): """Test the SideEffect() method""" diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 008976b..0c6627c 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1316,8 +1316,8 @@ class Dir(Base): """Return the .sconsign file info for this directory, creating it first if necessary.""" if not self._sconsign: - import SCons.Sig - self._sconsign = SCons.Sig.SConsignForDirectory(self) + import SCons.SConsign + self._sconsign = SCons.SConsign.ForDirectory(self) return self._sconsign def srcnode(self): diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py index 9a4162c..6790cb5 100644 --- a/src/engine/SCons/Node/__init__.py +++ b/src/engine/SCons/Node/__init__.py @@ -49,7 +49,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import copy from SCons.Debug import logInstanceCreation -import SCons.Sig +import SCons.SConsign import SCons.Util # Node states @@ -565,7 +565,7 @@ class Node: def get_prevsiginfo(self): """Fetch the previous signature information from the .sconsign entry.""" - return SCons.Sig._SConsign.null_siginfo + return SCons.SConsign.Base.null_siginfo def get_timestamp(self): return 0 diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py index b8502b0..8bc11ad 100644 --- a/src/engine/SCons/SConfTests.py +++ b/src/engine/SCons/SConfTests.py @@ -52,8 +52,8 @@ class SConfTestCase(unittest.TestCase): def _resetSConfState(self): # Ok, this is tricky, and i do not know, if everything is sane. # We try to reset scons' state (including all global variables) - import SCons.Sig - SCons.Sig.write() # simulate normal scons-finish + import SCons.SConsign + SCons.SConsign.write() # simulate normal scons-finish for n in sys.modules.keys(): if string.split(n, '.')[0] == 'SCons': m = sys.modules[n] diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py new file mode 100644 index 0000000..a91817b --- /dev/null +++ b/src/engine/SCons/SConsign.py @@ -0,0 +1,323 @@ +"""SCons.SConsign + +Writing and reading information to the .sconsign file or files. + +""" + +# +# __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 cPickle +import os +import os.path +import time + +import SCons.Sig +import SCons.Node +import SCons.Warnings + +#XXX Get rid of the global array so this becomes re-entrant. +sig_files = [] + +database = None + +def write(): + global sig_files + for sig_file in sig_files: + sig_file.write() + + +class Entry: + + """Objects of this type are pickled to the .sconsign file, so it + should only contain simple builtin Python datatypes and no methods. + + This class is used to store cache information about nodes between + scons runs for efficiency, and to store the build signature for + nodes so that scons can determine if they are out of date. """ + + # setup the default value for various attributes: + # (We make the class variables so the default values won't get pickled + # with the instances, which would waste a lot of space) + timestamp = None + bsig = None + csig = None + implicit = None + bkids = [] + bkidsigs = [] + bact = None + bactsig = None + +class Base: + """ + This is the controlling class for the signatures for the collection of + entries associated with a specific directory. The actual directory + association will be maintained by a subclass that is specific to + the underlying storage method. This class provides a common set of + methods for fetching and storing the individual bits of information + that make up signature entry. + """ + def __init__(self, module=None): + """ + module - the signature module being used + """ + + self.module = module or SCons.Sig.default_calc.module + self.entries = {} + self.dirty = 0 + + # A null .sconsign entry. We define this here so that it will + # be easy to keep this in sync if/whenever we change the type of + # information returned by the get() method, below. + null_siginfo = (None, None, None) + + def get(self, filename): + """ + Get the .sconsign entry for a file + + filename - the filename whose signature will be returned + returns - (timestamp, bsig, csig) + """ + entry = self.get_entry(filename) + return (entry.timestamp, entry.bsig, entry.csig) + + def get_entry(self, filename): + """ + Create an entry for the filename and return it, or if one already exists, + then return it. + """ + try: + return self.entries[filename] + except (KeyError, AttributeError): + return Entry() + + def set_entry(self, filename, entry): + """ + Set the entry. + """ + self.entries[filename] = entry + self.dirty = 1 + + def set_csig(self, filename, csig): + """ + Set the csig .sconsign entry for a file + + filename - the filename whose signature will be set + csig - the file's content signature + """ + + entry = self.get_entry(filename) + entry.csig = csig + self.set_entry(filename, entry) + + def set_binfo(self, filename, bsig, bkids, bkidsigs, bact, bactsig): + """ + Set the build info .sconsign entry for a file + + filename - the filename whose signature will be set + bsig - the file's built signature + """ + + entry = self.get_entry(filename) + entry.bsig = bsig + entry.bkids = bkids + entry.bkidsigs = bkidsigs + entry.bact = bact + entry.bactsig = bactsig + self.set_entry(filename, entry) + + def set_timestamp(self, filename, timestamp): + """ + Set the timestamp .sconsign entry for a file + + filename - the filename whose signature will be set + timestamp - the file's timestamp + """ + + entry = self.get_entry(filename) + entry.timestamp = timestamp + self.set_entry(filename, entry) + + def get_implicit(self, filename): + """Fetch the cached implicit dependencies for 'filename'""" + entry = self.get_entry(filename) + return entry.implicit + + def set_implicit(self, filename, implicit): + """Cache the implicit dependencies for 'filename'.""" + entry = self.get_entry(filename) + if not SCons.Util.is_List(implicit): + implicit = [implicit] + implicit = map(str, implicit) + entry.implicit = implicit + self.set_entry(filename, entry) + + def get_binfo(self, filename): + """Fetch the cached implicit dependencies for 'filename'""" + entry = self.get_entry(filename) + return entry.bsig, entry.bkids, entry.bkidsigs, entry.bact, entry.bactsig + +class DB(Base): + """ + A Base subclass that reads and writes signature information + from a global .sconsign.dbm file. + """ + def __init__(self, dir, module=None): + Base.__init__(self, module) + + self.dir = dir + + try: + global database + rawentries = database[self.dir.path] + except KeyError: + pass + else: + try: + self.entries = cPickle.loads(rawentries) + if type(self.entries) is not type({}): + self.entries = {} + raise TypeError + except KeyboardInterrupt: + raise + except: + SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, + "Ignoring corrupt sconsign entry : %s"%self.dir.path) + + global sig_files + sig_files.append(self) + + def write(self): + if self.dirty: + global database + database[self.dir.path] = cPickle.dumps(self.entries, 1) + try: + database.sync() + except AttributeError: + # Not all anydbm modules have sync() methods. + pass + +class Dir(Base): + def __init__(self, fp=None, module=None): + """ + fp - file pointer to read entries from + module - the signature module being used + """ + Base.__init__(self, module) + + if fp: + self.entries = cPickle.load(fp) + if type(self.entries) is not type({}): + self.entries = {} + raise TypeError + +class DirFile(Dir): + """ + Encapsulates reading and writing a per-directory .sconsign file. + """ + def __init__(self, dir, module=None): + """ + dir - the directory for the file + module - the signature module being used + """ + + self.dir = dir + self.sconsign = os.path.join(dir.path, '.sconsign') + + try: + fp = open(self.sconsign, 'rb') + except IOError: + fp = None + + try: + Dir.__init__(self, fp, module) + except KeyboardInterrupt: + raise + except: + SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, + "Ignoring corrupt .sconsign file: %s"%self.sconsign) + + global sig_files + sig_files.append(self) + + def write(self): + """ + Write the .sconsign file to disk. + + Try to write to a temporary file first, and rename it if we + succeed. If we can't write to the temporary file, it's + probably because the directory isn't writable (and if so, + how did we build anything in this directory, anyway?), so + try to write directly to the .sconsign file as a backup. + If we can't rename, try to copy the temporary contents back + to the .sconsign file. Either way, always try to remove + the temporary file at the end. + """ + if self.dirty: + temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) + try: + file = open(temp, 'wb') + fname = temp + except IOError: + try: + file = open(self.sconsign, 'wb') + fname = self.sconsign + except IOError: + return + cPickle.dump(self.entries, file, 1) + file.close() + if fname != self.sconsign: + try: + mode = os.stat(self.sconsign)[0] + os.chmod(self.sconsign, 0666) + os.unlink(self.sconsign) + except OSError: + pass + try: + os.rename(fname, self.sconsign) + except OSError: + open(self.sconsign, 'wb').write(open(fname, 'rb').read()) + os.chmod(self.sconsign, mode) + try: + os.unlink(temp) + except OSError: + pass + +ForDirectory = DirFile + +def File(name, dbm_module=None): + """ + Arrange for all signatures to be stored in a global .sconsign.dbm + file. + """ + global database + if database is None: + if dbm_module is None: + import SCons.dblite + dbm_module = SCons.dblite + database = dbm_module.open(name, "c") + + global ForDirectory + ForDirectory = DB diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py new file mode 100644 index 0000000..5f8e981 --- /dev/null +++ b/src/engine/SCons/SConsignTests.py @@ -0,0 +1,180 @@ +# +# __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 unittest +import TestCmd +import SCons.SConsign +import sys + +class SConsignEntryTestCase(unittest.TestCase): + + def runTest(self): + e = SCons.SConsign.Entry() + assert e.timestamp == None + assert e.csig == None + assert e.bsig == None + assert e.implicit == None + +class BaseTestCase(unittest.TestCase): + + def runTest(self): + class DummyModule: + def to_string(self, sig): + return str(sig) + + def from_string(self, sig): + return int(sig) + + class DummyNode: + path = 'not_a_valid_path' + + f = SCons.SConsign.Base() + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') + assert f.get('foo') == (None, 1, None) + f.set_csig('foo', 2) + assert f.get('foo') == (None, 1, 2) + f.set_timestamp('foo', 3) + assert f.get('foo') == (3, 1, 2) + f.set_implicit('foo', ['bar']) + assert f.get('foo') == (3, 1, 2) + assert f.get_implicit('foo') == ['bar'] + + f = SCons.SConsign.Base(DummyModule()) + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') + assert f.get('foo') == (None, 1, None) + f.set_csig('foo', 2) + assert f.get('foo') == (None, 1, 2) + f.set_timestamp('foo', 3) + assert f.get('foo') == (3, 1, 2) + f.set_implicit('foo', ['bar']) + assert f.get('foo') == (3, 1, 2) + assert f.get_implicit('foo') == ['bar'] + +class SConsignDBTestCase(unittest.TestCase): + + def runTest(self): + class DummyNode: + def __init__(self, path): + self.path = path + save_database = SCons.SConsign.database + SCons.SConsign.database = {} + try: + d1 = SCons.SConsign.DB(DummyNode('dir1')) + d1.set_timestamp('foo', 1) + d1.set_binfo('foo', 2, ['f1'], ['f2'], 'foo act', 'foo actsig') + d1.set_csig('foo', 3) + d1.set_timestamp('bar', 4) + d1.set_binfo('bar', 5, ['b1'], ['b2'], 'bar act', 'bar actsig') + d1.set_csig('bar', 6) + assert d1.get('foo') == (1, 2, 3) + assert d1.get('bar') == (4, 5, 6) + + d2 = SCons.SConsign.DB(DummyNode('dir1')) + d2.set_timestamp('foo', 7) + d2.set_binfo('foo', 8, ['f3'], ['f4'], 'foo act', 'foo actsig') + d2.set_csig('foo', 9) + d2.set_timestamp('bar', 10) + d2.set_binfo('bar', 11, ['b3'], ['b4'], 'bar act', 'bar actsig') + d2.set_csig('bar', 12) + assert d2.get('foo') == (7, 8, 9) + assert d2.get('bar') == (10, 11, 12) + finally: + SCons.SConsign.database = save_database + +class SConsignDirFileTestCase(unittest.TestCase): + + def runTest(self): + class DummyModule: + def to_string(self, sig): + return str(sig) + + def from_string(self, sig): + return int(sig) + + class DummyNode: + path = 'not_a_valid_path' + + f = SCons.SConsign.DirFile(DummyNode(), DummyModule()) + f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') + assert f.get('foo') == (None, 1, None) + f.set_csig('foo', 2) + assert f.get('foo') == (None, 1, 2) + f.set_timestamp('foo', 3) + assert f.get('foo') == (3, 1, 2) + f.set_implicit('foo', ['bar']) + assert f.get('foo') == (3, 1, 2) + assert f.get_implicit('foo') == ['bar'] + +class SConsignFileTestCase(unittest.TestCase): + + def runTest(self): + test = TestCmd.TestCmd(workdir = '') + file = test.workpath('sconsign_file') + + assert SCons.SConsign.database is None, SCons.SConsign.database + + SCons.SConsign.File(file) + + assert not SCons.SConsign.database is SCons.dblite, SCons.SConsign.database + + class Fake_DBM: + def open(self, name, mode): + self.name = name + self.mode = mode + return self + + fake_dbm = Fake_DBM() + + SCons.SConsign.File(file, fake_dbm) + + assert not SCons.SConsign.database is None, SCons.SConsign.database + assert not hasattr(fake_dbm, 'name'), fake_dbm + assert not hasattr(fake_dbm, 'mode'), fake_dbm + + SCons.SConsign.database = None + + SCons.SConsign.File(file, fake_dbm) + + assert not SCons.SConsign.database is None, SCons.SConsign.database + assert fake_dbm.name == file, fake_dbm.name + assert fake_dbm.mode == "c", fake_dbm.mode + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(SConsignEntryTestCase()) + suite.addTest(BaseTestCase()) + suite.addTest(SConsignDBTestCase()) + suite.addTest(SConsignDirFileTestCase()) + suite.addTest(SConsignFileTestCase()) + 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/Script/__init__.py b/src/engine/SCons/Script/__init__.py index ee1ec81..3a7d4c1 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -1114,7 +1114,7 @@ def _main(args, parser): else: progress_display("scons: " + closing_message) if not options.noexec: - SCons.Sig.write() + SCons.SConsign.write() if not memory_stats is None: memory_stats.append(SCons.Debug.memory()) diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py index 76cd931..009addd 100644 --- a/src/engine/SCons/Sig/SigTests.py +++ b/src/engine/SCons/Sig/SigTests.py @@ -393,139 +393,6 @@ class CalcTestCase(unittest.TestCase): nn.always_build = 1 assert not self.calc.current(nn, 33) -class SConsignEntryTestCase(unittest.TestCase): - - def runTest(self): - e = SCons.Sig.SConsignEntry() - assert e.timestamp == None - assert e.csig == None - assert e.bsig == None - assert e.implicit == None - -class _SConsignTestCase(unittest.TestCase): - - def runTest(self): - class DummyModule: - def to_string(self, sig): - return str(sig) - - def from_string(self, sig): - return int(sig) - - class DummyNode: - path = 'not_a_valid_path' - - f = SCons.Sig._SConsign() - f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') - assert f.get('foo') == (None, 1, None) - f.set_csig('foo', 2) - assert f.get('foo') == (None, 1, 2) - f.set_timestamp('foo', 3) - assert f.get('foo') == (3, 1, 2) - f.set_implicit('foo', ['bar']) - assert f.get('foo') == (3, 1, 2) - assert f.get_implicit('foo') == ['bar'] - - f = SCons.Sig._SConsign(DummyModule()) - f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') - assert f.get('foo') == (None, 1, None) - f.set_csig('foo', 2) - assert f.get('foo') == (None, 1, 2) - f.set_timestamp('foo', 3) - assert f.get('foo') == (3, 1, 2) - f.set_implicit('foo', ['bar']) - assert f.get('foo') == (3, 1, 2) - assert f.get_implicit('foo') == ['bar'] - -class SConsignDBTestCase(unittest.TestCase): - - def runTest(self): - class DummyNode: - def __init__(self, path): - self.path = path - save_SConsign_db = SCons.Sig.SConsign_db - SCons.Sig.SConsign_db = {} - try: - d1 = SCons.Sig.SConsignDB(DummyNode('dir1')) - d1.set_timestamp('foo', 1) - d1.set_binfo('foo', 2, ['f1'], ['f2'], 'foo act', 'foo actsig') - d1.set_csig('foo', 3) - d1.set_timestamp('bar', 4) - d1.set_binfo('bar', 5, ['b1'], ['b2'], 'bar act', 'bar actsig') - d1.set_csig('bar', 6) - assert d1.get('foo') == (1, 2, 3) - assert d1.get('bar') == (4, 5, 6) - - d2 = SCons.Sig.SConsignDB(DummyNode('dir1')) - d2.set_timestamp('foo', 7) - d2.set_binfo('foo', 8, ['f3'], ['f4'], 'foo act', 'foo actsig') - d2.set_csig('foo', 9) - d2.set_timestamp('bar', 10) - d2.set_binfo('bar', 11, ['b3'], ['b4'], 'bar act', 'bar actsig') - d2.set_csig('bar', 12) - assert d2.get('foo') == (7, 8, 9) - assert d2.get('bar') == (10, 11, 12) - finally: - SCons.Sig.SConsign_db = save_SConsign_db - -class SConsignDirFileTestCase(unittest.TestCase): - - def runTest(self): - class DummyModule: - def to_string(self, sig): - return str(sig) - - def from_string(self, sig): - return int(sig) - - class DummyNode: - path = 'not_a_valid_path' - - f = SCons.Sig.SConsignDirFile(DummyNode(), DummyModule()) - f.set_binfo('foo', 1, ['f1'], ['f2'], 'foo act', 'foo actsig') - assert f.get('foo') == (None, 1, None) - f.set_csig('foo', 2) - assert f.get('foo') == (None, 1, 2) - f.set_timestamp('foo', 3) - assert f.get('foo') == (3, 1, 2) - f.set_implicit('foo', ['bar']) - assert f.get('foo') == (3, 1, 2) - assert f.get_implicit('foo') == ['bar'] - -class SConsignFileTestCase(unittest.TestCase): - - def runTest(self): - test = TestCmd.TestCmd(workdir = '') - file = test.workpath('sconsign_file') - - assert SCons.Sig.SConsign_db is None, SCons.Sig.SConsign_db - - SCons.Sig.SConsignFile(file) - - assert not SCons.Sig.SConsign_db is SCons.dblite, SCons.Sig.SConsign_db - - class Fake_DBM: - def open(self, name, mode): - self.name = name - self.mode = mode - return self - - fake_dbm = Fake_DBM() - - SCons.Sig.SConsignFile(file, fake_dbm) - - assert not SCons.Sig.SConsign_db is None, SCons.Sig.SConsign_db - assert not hasattr(fake_dbm, 'name'), fake_dbm - assert not hasattr(fake_dbm, 'mode'), fake_dbm - - SCons.Sig.SConsign_db = None - - SCons.Sig.SConsignFile(file, fake_dbm) - - assert not SCons.Sig.SConsign_db is None, SCons.Sig.SConsign_db - assert fake_dbm.name == file, fake_dbm.name - assert fake_dbm.mode == "c", fake_dbm.mode - def suite(): @@ -533,11 +400,6 @@ def suite(): suite.addTest(MD5TestCase()) suite.addTest(TimeStampTestCase()) suite.addTest(CalcTestCase()) - suite.addTest(SConsignEntryTestCase()) - suite.addTest(_SConsignTestCase()) - suite.addTest(SConsignDBTestCase()) - suite.addTest(SConsignDirFileTestCase()) - suite.addTest(SConsignFileTestCase()) return suite if __name__ == "__main__": diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py index dac3f6c..271f252 100644 --- a/src/engine/SCons/Sig/__init__.py +++ b/src/engine/SCons/Sig/__init__.py @@ -46,293 +46,6 @@ except ImportError: default_max_drift = 2*24*60*60 -#XXX Get rid of the global array so this becomes re-entrant. -sig_files = [] - -SConsign_db = None - -def write(): - global sig_files - for sig_file in sig_files: - sig_file.write() - - -class SConsignEntry: - - """Objects of this type are pickled to the .sconsign file, so it - should only contain simple builtin Python datatypes and no methods. - - This class is used to store cache information about nodes between - scons runs for efficiency, and to store the build signature for - nodes so that scons can determine if they are out of date. """ - - # setup the default value for various attributes: - # (We make the class variables so the default values won't get pickled - # with the instances, which would waste a lot of space) - timestamp = None - bsig = None - csig = None - implicit = None - bkids = [] - bkidsigs = [] - bact = None - bactsig = None - -class _SConsign: - """ - This is the controlling class for the signatures for the collection of - entries associated with a specific directory. The actual directory - association will be maintained by a subclass that is specific to - the underlying storage method. This class provides a common set of - methods for fetching and storing the individual bits of information - that make up signature entry. - """ - def __init__(self, module=None): - """ - module - the signature module being used - """ - - if module is None: - self.module = default_calc.module - else: - self.module = module - self.entries = {} - self.dirty = 0 - - # A null .sconsign entry. We define this here so that it will - # be easy to keep this in sync if/whenever we change the type of - # information returned by the get() method, below. - null_siginfo = (None, None, None) - - def get(self, filename): - """ - Get the .sconsign entry for a file - - filename - the filename whose signature will be returned - returns - (timestamp, bsig, csig) - """ - entry = self.get_entry(filename) - return (entry.timestamp, entry.bsig, entry.csig) - - def get_entry(self, filename): - """ - Create an entry for the filename and return it, or if one already exists, - then return it. - """ - try: - return self.entries[filename] - except (KeyError, AttributeError): - return SConsignEntry() - - def set_entry(self, filename, entry): - """ - Set the entry. - """ - self.entries[filename] = entry - self.dirty = 1 - - def set_csig(self, filename, csig): - """ - Set the csig .sconsign entry for a file - - filename - the filename whose signature will be set - csig - the file's content signature - """ - - entry = self.get_entry(filename) - entry.csig = csig - self.set_entry(filename, entry) - - def set_binfo(self, filename, bsig, bkids, bkidsigs, bact, bactsig): - """ - Set the build info .sconsign entry for a file - - filename - the filename whose signature will be set - bsig - the file's built signature - """ - - entry = self.get_entry(filename) - entry.bsig = bsig - entry.bkids = bkids - entry.bkidsigs = bkidsigs - entry.bact = bact - entry.bactsig = bactsig - self.set_entry(filename, entry) - - def set_timestamp(self, filename, timestamp): - """ - Set the timestamp .sconsign entry for a file - - filename - the filename whose signature will be set - timestamp - the file's timestamp - """ - - entry = self.get_entry(filename) - entry.timestamp = timestamp - self.set_entry(filename, entry) - - def get_implicit(self, filename): - """Fetch the cached implicit dependencies for 'filename'""" - entry = self.get_entry(filename) - return entry.implicit - - def set_implicit(self, filename, implicit): - """Cache the implicit dependencies for 'filename'.""" - entry = self.get_entry(filename) - if not SCons.Util.is_List(implicit): - implicit = [implicit] - implicit = map(str, implicit) - entry.implicit = implicit - self.set_entry(filename, entry) - - def get_binfo(self, filename): - """Fetch the cached implicit dependencies for 'filename'""" - entry = self.get_entry(filename) - return entry.bsig, entry.bkids, entry.bkidsigs, entry.bact, entry.bactsig - -class SConsignDB(_SConsign): - """ - A _SConsign subclass that reads and writes signature information - from a global .sconsign.dbm file. - """ - def __init__(self, dir, module=None): - _SConsign.__init__(self, module) - - self.dir = dir - - try: - global SConsign_db - rawentries = SConsign_db[self.dir.path] - except KeyError: - pass - else: - try: - self.entries = cPickle.loads(rawentries) - if type(self.entries) is not type({}): - self.entries = {} - raise TypeError - except KeyboardInterrupt: - raise - except: - SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, - "Ignoring corrupt sconsign entry : %s"%self.dir.path) - - global sig_files - sig_files.append(self) - - def write(self): - if self.dirty: - global SConsign_db - SConsign_db[self.dir.path] = cPickle.dumps(self.entries, 1) - try: - SConsign_db.sync() - except AttributeError: - # Not all anydbm modules have sync() methods. - pass - -class SConsignDir(_SConsign): - def __init__(self, fp=None, module=None): - """ - fp - file pointer to read entries from - module - the signature module being used - """ - _SConsign.__init__(self, module) - - if fp: - self.entries = cPickle.load(fp) - if type(self.entries) is not type({}): - self.entries = {} - raise TypeError - -class SConsignDirFile(SConsignDir): - """ - Encapsulates reading and writing a per-directory .sconsign file. - """ - def __init__(self, dir, module=None): - """ - dir - the directory for the file - module - the signature module being used - """ - - self.dir = dir - self.sconsign = os.path.join(dir.path, '.sconsign') - - try: - fp = open(self.sconsign, 'rb') - except IOError: - fp = None - - try: - SConsignDir.__init__(self, fp, module) - except KeyboardInterrupt: - raise - except: - SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, - "Ignoring corrupt .sconsign file: %s"%self.sconsign) - - global sig_files - sig_files.append(self) - - def write(self): - """ - Write the .sconsign file to disk. - - Try to write to a temporary file first, and rename it if we - succeed. If we can't write to the temporary file, it's - probably because the directory isn't writable (and if so, - how did we build anything in this directory, anyway?), so - try to write directly to the .sconsign file as a backup. - If we can't rename, try to copy the temporary contents back - to the .sconsign file. Either way, always try to remove - the temporary file at the end. - """ - if self.dirty: - temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) - try: - file = open(temp, 'wb') - fname = temp - except IOError: - try: - file = open(self.sconsign, 'wb') - fname = self.sconsign - except IOError: - return - cPickle.dump(self.entries, file, 1) - file.close() - if fname != self.sconsign: - try: - mode = os.stat(self.sconsign)[0] - os.chmod(self.sconsign, 0666) - os.unlink(self.sconsign) - except OSError: - pass - try: - os.rename(fname, self.sconsign) - except OSError: - open(self.sconsign, 'wb').write(open(fname, 'rb').read()) - os.chmod(self.sconsign, mode) - try: - os.unlink(temp) - except OSError: - pass - -SConsignForDirectory = SConsignDirFile - -def SConsignFile(name, dbm_module=None): - """ - Arrange for all signatures to be stored in a global .sconsign.dbm - file. - """ - global SConsign_db - if SConsign_db is None: - if dbm_module is None: - import SCons.dblite - dbm_module = SCons.dblite - SConsign_db = dbm_module.open(name, "c") - - global SConsignForDirectory - SConsignForDirectory = SConsignDB - class Calculator: """ Encapsulates signature calculations and .sconsign file generating @@ -420,7 +133,8 @@ class Calculator: if self.max_drift >= 0: oldtime, oldbsig, oldcsig = node.get_prevsiginfo() else: - oldtime, oldbsig, oldcsig = _SConsign.null_siginfo + import SCons.SConsign + oldtime, oldbsig, oldcsig = SCons.SConsign.Base.null_siginfo mtime = node.get_timestamp() diff --git a/src/script/sconsign.py b/src/script/sconsign.py index e3905ce..2204927 100644 --- a/src/script/sconsign.py +++ b/src/script/sconsign.py @@ -146,7 +146,7 @@ import imp import string import whichdb -import SCons.Sig +import SCons.SConsign def my_whichdb(filename): try: @@ -286,7 +286,7 @@ def Do_SConsignDir(name): sys.stderr.write("sconsign: %s\n" % (e)) return try: - sconsign = SCons.Sig.SConsignDir(fp) + sconsign = SCons.SConsign.Dir(fp) except: sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % name) return -- cgit v0.12