From f6d11d5411025f9d11afa0804f6099adca6a48d2 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Fri, 10 Dec 2010 22:42:33 -0800 Subject: [PATCH] If a user manually changes a generated file, don't ever overwrite it. That way the user can modify an auto-generated 'compile' script, for example, and it'll stay modified. If they delete the file, we can then generate it for them again. Also, we have to warn whenever we're doing this, or people might think it's a bug. --- builder.py | 17 +++++++++++++++-- redo-ifchange.py | 5 +++++ state.py | 31 +++++++++++++++++++++++-------- t/flush-cache.sh | 2 +- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/builder.py b/builder.py index 06ccbb3..416120f 100644 --- a/builder.py +++ b/builder.py @@ -42,6 +42,10 @@ def _try_stat(filename): raise +def warn_override(name): + warn('%s - you modified it; skipping\n' % name) + + class ImmediateReturn(Exception): def __init__(self, rv): Exception.__init__(self, "immediate return with exit code %d" % rv) @@ -69,6 +73,16 @@ class BuildJob: return self._after2(0) except ImmediateReturn, e: return self._after2(e.rv) + newstamp = sf.read_stamp() + if (sf.is_generated and + not sf.failed_runid and + newstamp != state.STAMP_MISSING and + (sf.stamp != newstamp or sf.is_override)): + warn_override(_nice(t)) + sf.set_override() + sf.set_checked() + sf.save() + return self._after2(0) if (os.path.exists(t) and not os.path.exists(t + '/.') and not sf.is_generated): # an existing source file that was not generated by us. @@ -87,7 +101,6 @@ class BuildJob: (dofile, basename, ext) = _find_do_file(sf) if not dofile: if os.path.exists(t): - sf.is_generated = False sf.set_static() sf.save() return self._after2(0) @@ -176,7 +189,7 @@ class BuildJob: else: unlink(tmpname) sf = self.sf - sf.is_generated=True + sf.is_generated = True sf.update_stamp() sf.set_changed() sf.save() diff --git a/redo-ifchange.py b/redo-ifchange.py index fb5af22..1670163 100755 --- a/redo-ifchange.py +++ b/redo-ifchange.py @@ -7,6 +7,9 @@ from helpers import debug, debug2, err, unlink def dirty_deps(f, depth, max_changed): if vars.DEBUG >= 1: debug('%s?%s\n' % (depth, f.name)) + if f.failed_runid: + debug('%s-- DIRTY (failed last time)\n' % depth) + return True if f.changed_runid == None: debug('%s-- DIRTY (never built)\n' % depth) return True @@ -35,6 +38,8 @@ def dirty_deps(f, depth, max_changed): max_changed = f.changed_runid): debug('%s-- DIRTY (sub)\n' % depth) return True + if f.is_override: + builder.warn_override(f.name) f.set_checked() f.save() return False diff --git a/state.py b/state.py index 4ecc254..d6bd56c 100644 --- a/state.py +++ b/state.py @@ -6,6 +6,10 @@ import helpers SCHEMA_VER=1 TIMEOUT=60 +STAMP_DIR='dir' # the stamp of a directory; mtime is unhelpful +STAMP_MISSING='0' # the stamp of a nonexistent file + + def _connect(dbfile): _db = sqlite3.connect(dbfile, timeout=TIMEOUT) _db.execute("pragma synchronous = off") @@ -57,6 +61,7 @@ def db(): _db.execute("create table Files " " (name not null primary key, " " is_generated int, " + " is_override int, " " checked_runid int, " " changed_runid int, " " failed_runid int, " @@ -134,19 +139,19 @@ def relpath(t, base): class File(object): # use this mostly to avoid accidentally assigning to typos - __slots__ = ['id', 'name', 'is_generated', + __slots__ = ['id', 'name', 'is_generated', 'is_override', 'checked_runid', 'changed_runid', 'failed_runid', 'stamp', 'csum'] def _init_from_cols(self, cols): - (self.id, self.name, self.is_generated, + (self.id, self.name, self.is_generated, self.is_override, self.checked_runid, self.changed_runid, self.failed_runid, self.stamp, self.csum) = cols def __init__(self, id=None, name=None, cols=None): if cols: return self._init_from_cols(cols) - q = ('select rowid, name, is_generated, ' + q = ('select rowid, name, is_generated, is_override, ' ' checked_runid, changed_runid, failed_runid, ' ' stamp, csum ' ' from Files ') @@ -177,11 +182,11 @@ class File(object): def save(self): _write('update Files set ' - ' is_generated=?, ' + ' is_generated=?, is_override=?, ' ' checked_runid=?, changed_runid=?, failed_runid=?, ' ' stamp=?, csum=? ' ' where rowid=?', - [self.is_generated, + [self.is_generated, self.is_override, self.checked_runid, self.changed_runid, self.failed_runid, self.stamp, self.csum, self.id]) @@ -192,13 +197,23 @@ class File(object): def set_changed(self): debug2('BUILT: %r (%r)\n' % (self.name, self.stamp)) self.changed_runid = vars.RUNID + self.failed_runid = None + self.is_override = False def set_failed(self): debug2('FAILED: %r\n' % self.name) + self.update_stamp() self.failed_runid = vars.RUNID + self.is_generated = True def set_static(self): self.update_stamp() + self.is_override = False + self.is_generated = False + + def set_override(self): + self.update_stamp() + self.is_override = True def update_stamp(self): newstamp = self.read_stamp() @@ -218,7 +233,7 @@ class File(object): def deps(self): q = ('select Deps.mode, Deps.source, ' - ' name, is_generated, ' + ' name, is_generated, is_override, ' ' checked_runid, changed_runid, failed_runid, ' ' stamp, csum ' ' from Files ' @@ -247,9 +262,9 @@ class File(object): try: st = os.stat(os.path.join(vars.BASE, self.name)) except OSError: - return '0' # does not exist + return STAMP_MISSING if stat.S_ISDIR(st.st_mode): - return 'dir' # the timestamp of a directory is meaningless + return STAMP_DIR else: # a "unique identifier" stamp for a regular file return str((st.st_ctime, st.st_mtime, st.st_size, st.st_ino)) diff --git a/t/flush-cache.sh b/t/flush-cache.sh index f44286d..4a28210 100755 --- a/t/flush-cache.sh +++ b/t/flush-cache.sh @@ -5,5 +5,5 @@ echo "pragma synchronous = off;" echo "update Files set checked_runid=null, " \ " changed_runid=changed_runid-1, " \ - " failed_runid=null;" + " failed_runid=failed_runid-1;" ) | sqlite3 "$REDO_BASE/.redo/db.sqlite3"