diff --git a/builder.py b/builder.py index 9236f5c..3d942a7 100644 --- a/builder.py +++ b/builder.py @@ -43,10 +43,6 @@ 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) @@ -88,7 +84,7 @@ class BuildJob: not sf.failed_runid and newstamp != state.STAMP_MISSING and (sf.stamp != newstamp or sf.is_override)): - warn_override(_nice(t)) + state.warn_override(_nice(t)) sf.set_override() sf.set_checked() sf.save() diff --git a/deps.py b/deps.py new file mode 100644 index 0000000..ffedfc4 --- /dev/null +++ b/deps.py @@ -0,0 +1,91 @@ +import sys, os +import vars, state, builder +from log import debug + +CLEAN = 0 +DIRTY = 1 + +def isdirty(f, depth, max_changed): + if vars.DEBUG >= 1: + debug('%s?%s\n' % (depth, f.nicename())) + + if f.failed_runid: + debug('%s-- DIRTY (failed last time)\n' % depth) + return DIRTY + if f.changed_runid == None: + debug('%s-- DIRTY (never built)\n' % depth) + return DIRTY + if f.changed_runid > max_changed: + debug('%s-- DIRTY (built)\n' % depth) + return DIRTY # has been built more recently than parent + if f.is_checked(): + if vars.DEBUG >= 1: + debug('%s-- CLEAN (checked)\n' % depth) + return CLEAN # has already been checked during this session + if not f.stamp: + debug('%s-- DIRTY (no stamp)\n' % depth) + return DIRTY + + newstamp = f.read_stamp() + if f.stamp != newstamp: + if newstamp == state.STAMP_MISSING: + debug('%s-- DIRTY (missing)\n' % depth) + else: + debug('%s-- DIRTY (mtime)\n' % depth) + if f.csum: + return [f] + else: + return DIRTY + + must_build = [] + for mode,f2 in f.deps(): + dirty = CLEAN + if mode == 'c': + if os.path.exists(os.path.join(vars.BASE, f2.name)): + debug('%s-- DIRTY (created)\n' % depth) + dirty = DIRTY + elif mode == 'm': + sub = isdirty(f2, depth = depth + ' ', + max_changed = max(f.changed_runid, + f.checked_runid)) + if sub: + debug('%s-- DIRTY (sub)\n' % depth) + dirty = sub + else: + assert(mode in ('c','m')) + if not f.csum: + # f is a "normal" target: dirty f2 means f is instantly dirty + if dirty: + # if dirty==DIRTY, this means f is definitely dirty. + # if dirty==[...], it's a list of the uncertain children. + return dirty + else: + # f is "checksummable": dirty f2 means f needs to redo, + # but f might turn out to be clean after that (ie. our parent + # might not be dirty). + if dirty == DIRTY: + # f2 is definitely dirty, so f definitely needs to + # redo. However, after that, f might turn out to be + # unchanged. + return [f] + elif isinstance(dirty,list): + # our child f2 might be dirty, but it's not sure yet. It's + # given us a list of targets we have to redo in order to + # be sure. + must_build += dirty + + if must_build: + # f is *maybe* dirty because at least one of its children is maybe + # dirty. must_build has accumulated a list of "topmost" uncertain + # objects in the tree. If we build all those, we can then + # redo-ifchange f and it won't have any uncertainty next time. + return must_build + + # if we get here, it's because the target is clean + if f.is_override: + state.warn_override(f.name) + f.set_checked() + f.save() + return CLEAN + + diff --git a/redo-ifchange.py b/redo-ifchange.py index a3cc7a7..cd3e71e 100755 --- a/redo-ifchange.py +++ b/redo-ifchange.py @@ -1,107 +1,21 @@ #!/usr/bin/python -import sys, os, errno, stat +import sys, os if not sys.argv[1:]: sys.exit(0) # nothing to do, so we can't possibly do it wrong import vars_init vars_init.init(sys.argv[1:]) -import vars, state, builder, jwack +import vars, state, builder, jwack, deps from helpers import unlink from log import debug, debug2, err -CLEAN = 0 -DIRTY = 1 -def dirty_deps(f, depth, max_changed): - if vars.DEBUG >= 1: - debug('%s?%s\n' % (depth, f.nicename())) - - if f.failed_runid: - debug('%s-- DIRTY (failed last time)\n' % depth) - return DIRTY - if f.changed_runid == None: - debug('%s-- DIRTY (never built)\n' % depth) - return DIRTY - if f.changed_runid > max_changed: - debug('%s-- DIRTY (built)\n' % depth) - return DIRTY # has been built more recently than parent - if f.is_checked(): - if vars.DEBUG >= 1: - debug('%s-- CLEAN (checked)\n' % depth) - return CLEAN # has already been checked during this session - if not f.stamp: - debug('%s-- DIRTY (no stamp)\n' % depth) - return DIRTY - - newstamp = f.read_stamp() - if f.stamp != newstamp: - if newstamp == state.STAMP_MISSING: - debug('%s-- DIRTY (missing)\n' % depth) - else: - debug('%s-- DIRTY (mtime)\n' % depth) - if f.csum: - return [f] - else: - return DIRTY - - must_build = [] - for mode,f2 in f.deps(): - dirty = CLEAN - if mode == 'c': - if os.path.exists(os.path.join(vars.BASE, f2.name)): - debug('%s-- DIRTY (created)\n' % depth) - dirty = DIRTY - elif mode == 'm': - sub = dirty_deps(f2, depth = depth + ' ', - max_changed = max(f.changed_runid, - f.checked_runid)) - if sub: - debug('%s-- DIRTY (sub)\n' % depth) - dirty = sub - else: - assert(mode in ('c','m')) - if not f.csum: - # f is a "normal" target: dirty f2 means f is instantly dirty - if dirty: - # if dirty==DIRTY, this means f is definitely dirty. - # if dirty==[...], it's a list of the uncertain children. - return dirty - else: - # f is "checksummable": dirty f2 means f needs to redo, - # but f might turn out to be clean after that (ie. our parent - # might not be dirty). - if dirty == DIRTY: - # f2 is definitely dirty, so f definitely needs to - # redo. However, after that, f might turn out to be - # unchanged. - return [f] - elif isinstance(dirty,list): - # our child f2 might be dirty, but it's not sure yet. It's - # given us a list of targets we have to redo in order to - # be sure. - must_build += dirty - - if must_build: - # f is *maybe* dirty because at least one of its children is maybe - # dirty. must_build has accumulated a list of "topmost" uncertain - # objects in the tree. If we build all those, we can then - # redo-ifchange f and it won't have any uncertainty next time. - return must_build - - # if we get here, it's because the target is clean - if f.is_override: - builder.warn_override(f.name) - f.set_checked() - f.save() - return CLEAN - - def should_build(t): f = state.File(name=t) if f.is_failed(): raise builder.ImmediateReturn(32) - dirty = dirty_deps(f, depth = '', max_changed = vars.RUNID) - return dirty==[f] and DIRTY or dirty + dirty = deps.isdirty(f, depth = '', max_changed = vars.RUNID) + return dirty==[f] and deps.DIRTY or dirty rv = 202 diff --git a/state.py b/state.py index 6d8b137..3d3eaea 100644 --- a/state.py +++ b/state.py @@ -1,7 +1,7 @@ import sys, os, errno, glob, stat, fcntl, sqlite3 import vars from helpers import unlink, close_on_exec, join -from log import err, debug2, debug3 +from log import warn, err, debug2, debug3 SCHEMA_VER=1 TIMEOUT=60 @@ -132,6 +132,10 @@ def relpath(t, base): return join('/', tparts) +def warn_override(name): + warn('%s - you modified it; skipping\n' % name) + + _file_cols = ['rowid', 'name', 'is_generated', 'is_override', 'checked_runid', 'changed_runid', 'failed_runid', 'stamp', 'csum']