Move dependency checking from redo-ifchange into deps.py.

In preparation for sharing between multiple commands.
This commit is contained in:
Avery Pennarun 2010-12-19 02:31:40 -08:00
commit df85b3d163
4 changed files with 101 additions and 96 deletions

View file

@ -43,10 +43,6 @@ def _try_stat(filename):
raise raise
def warn_override(name):
warn('%s - you modified it; skipping\n' % name)
class ImmediateReturn(Exception): class ImmediateReturn(Exception):
def __init__(self, rv): def __init__(self, rv):
Exception.__init__(self, "immediate return with exit code %d" % rv) Exception.__init__(self, "immediate return with exit code %d" % rv)
@ -88,7 +84,7 @@ class BuildJob:
not sf.failed_runid and not sf.failed_runid and
newstamp != state.STAMP_MISSING and newstamp != state.STAMP_MISSING and
(sf.stamp != newstamp or sf.is_override)): (sf.stamp != newstamp or sf.is_override)):
warn_override(_nice(t)) state.warn_override(_nice(t))
sf.set_override() sf.set_override()
sf.set_checked() sf.set_checked()
sf.save() sf.save()

91
deps.py Normal file
View file

@ -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

View file

@ -1,107 +1,21 @@
#!/usr/bin/python #!/usr/bin/python
import sys, os, errno, stat import sys, os
if not sys.argv[1:]: if not sys.argv[1:]:
sys.exit(0) # nothing to do, so we can't possibly do it wrong sys.exit(0) # nothing to do, so we can't possibly do it wrong
import vars_init import vars_init
vars_init.init(sys.argv[1:]) vars_init.init(sys.argv[1:])
import vars, state, builder, jwack import vars, state, builder, jwack, deps
from helpers import unlink from helpers import unlink
from log import debug, debug2, err 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): def should_build(t):
f = state.File(name=t) f = state.File(name=t)
if f.is_failed(): if f.is_failed():
raise builder.ImmediateReturn(32) raise builder.ImmediateReturn(32)
dirty = dirty_deps(f, depth = '', max_changed = vars.RUNID) dirty = deps.isdirty(f, depth = '', max_changed = vars.RUNID)
return dirty==[f] and DIRTY or dirty return dirty==[f] and deps.DIRTY or dirty
rv = 202 rv = 202

View file

@ -1,7 +1,7 @@
import sys, os, errno, glob, stat, fcntl, sqlite3 import sys, os, errno, glob, stat, fcntl, sqlite3
import vars import vars
from helpers import unlink, close_on_exec, join from helpers import unlink, close_on_exec, join
from log import err, debug2, debug3 from log import warn, err, debug2, debug3
SCHEMA_VER=1 SCHEMA_VER=1
TIMEOUT=60 TIMEOUT=60
@ -132,6 +132,10 @@ def relpath(t, base):
return join('/', tparts) return join('/', tparts)
def warn_override(name):
warn('%s - you modified it; skipping\n' % name)
_file_cols = ['rowid', 'name', 'is_generated', 'is_override', _file_cols = ['rowid', 'name', 'is_generated', 'is_override',
'checked_runid', 'changed_runid', 'failed_runid', 'checked_runid', 'changed_runid', 'failed_runid',
'stamp', 'csum'] 'stamp', 'csum']