From 9a45f066f82eca0519cb812487b0ce13746b97fb Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Fri, 12 Nov 2010 07:03:06 -0800 Subject: [PATCH] Add actual dependency checking. --- helpers.py | 24 +++++++--- redo.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 130 insertions(+), 20 deletions(-) diff --git a/helpers.py b/helpers.py index ea9c09f..16ad39e 100644 --- a/helpers.py +++ b/helpers.py @@ -1,12 +1,6 @@ import sys, os, errno -def log(s): - sys.stdout.flush() - sys.stderr.write(s) - sys.stderr.flush() - - def unlink(f): """Delete a file at path 'f' if it currently exists. @@ -20,3 +14,21 @@ def unlink(f): pass # it doesn't exist, that's what you asked for +def mkdirp(d, mode=None): + """Recursively create directories on path 'd'. + + Unlike os.makedirs(), it doesn't raise an exception if the last element of + the path already exists. + """ + try: + if mode: + os.makedirs(d, mode) + else: + os.makedirs(d) + except OSError, e: + if e.errno == errno.EEXIST: + pass + else: + raise + + diff --git a/redo.py b/redo.py index ff9146a..7a5f59f 100755 --- a/redo.py +++ b/redo.py @@ -6,7 +6,9 @@ from helpers import * optspec = """ redo [targets...] -- -ifchange something something +d,debug print dependency checks as they happen +ifchange only redo if the file is modified or deleted +ifcreate only redo if the file is created """ o = options.Options('redo', optspec) (opt, flags, extra) = o.parse(sys.argv[1:]) @@ -14,41 +16,137 @@ o = options.Options('redo', optspec) targets = extra or ['it'] -def find_do_file(t): - p = '%s.do' % t - if os.path.exists(p): - return p - else: - return None +def log(s): + sys.stdout.flush() + sys.stderr.write('redo: %s%s' % (REDO_DEPTH, s)) + sys.stderr.flush() + + +def debug(s): + if REDO_DEBUG: + log(s) + + +def add_dep(t, mode, dep): + open('.redo/dep.%s' % t, 'a').write('%s %s\n' % (mode, dep)) +def find_do_file(t): + dofile = '%s.do' % t + if os.path.exists(dofile): + add_dep(t, 'm', dofile) + if dirty_deps(dofile, depth = ''): + build(dofile) + return dofile + else: + add_dep(t, 'c', dofile) + return None + + +def _dirty_deps(t, depth): + debug('%s?%s\n' % (depth, t)) + if not os.path.exists('.redo/stamp.%s' % t): + debug('%s-- DIRTY (no stamp)\n' % depth) + return True + + stamptime = os.stat('.redo/stamp.%s' % t).st_mtime + try: + realtime = os.stat(t).st_mtime + except OSError: + realtime = 0 + + if stamptime != realtime: + debug('%s-- DIRTY (mtime)\n' % depth) + return True + + for sub in open('.redo/dep.%s' % t).readlines(): + assert(sub[0] in ('c','m')) + assert(sub[1] == ' ') + assert(sub[-1] == '\n') + mode = sub[0] + name = sub[2:-1] + if mode == 'c': + if os.path.exists(name): + debug('%s-- DIRTY (created)\n' % depth) + return True + elif mode == 'm': + if dirty_deps(name, depth + ' '): + #debug('%s-- DIRTY (sub)\n' % depth) + return True + return False + + +def dirty_deps(t, depth): + if _dirty_deps(t, depth): + unlink('.redo/stamp.%s' % t) # short circuit future checks + return True + return False + + +def stamp(t): + stampfile = '.redo/stamp.%s' % t + open(stampfile, 'w').close() + try: + mtime = os.stat(t).st_mtime + except OSError: + mtime = 0 + os.utime(stampfile, (mtime, mtime)) + + def build(t): + unlink('.redo/dep.%s' % t) + open('.redo/dep.%s' % t, 'w').close() dofile = find_do_file(t) if not dofile: if os.path.exists(t): # an existing source file + stamp(t) return # success else: raise Exception('no rule to make %r' % t) unlink(t) os.putenv('REDO_TARGET', t) - depth = os.getenv('REDO_DEPTH', '') - os.putenv('REDO_DEPTH', depth + ' ') + os.putenv('REDO_DEPTH', REDO_DEPTH + ' ') tmpname = '%s.redo.tmp' % t unlink(tmpname) f = open(tmpname, 'w+') argv = ['sh', '-e', dofile, t, 'FIXME', tmpname] - log('redo: %s%s\n' % (depth, t)) + log('%s\n' % t) rv = subprocess.call(argv, stdout=f.fileno()) st = os.stat(tmpname) #log('rv: %d (%d bytes) (%r)\n' % (rv, st.st_size, dofile)) - if rv==0 and st.st_size: - os.rename(tmpname, t) - #log('made %r\n' % t) + stampfile = '.redo/stamp.%s' % t + if rv==0: + if st.st_size: + os.rename(tmpname, t) + else: + unlink(tmpname) + stamp(t) else: unlink(tmpname) + unlink(stampfile) f.close() if rv != 0: raise Exception('non-zero return code building %r' % t) + +mkdirp('.redo') +REDO_TARGET = os.getenv('REDO_TARGET', '') +REDO_DEPTH = os.getenv('REDO_DEPTH', '') + +assert(not (opt.ifchange and opt.ifcreate)) +if opt.debug: + REDO_DEBUG = 1 + os.putenv('REDO_DEBUG', '1') +else: + REDO_DEBUG = os.getenv('REDO_DEBUG', '') and 1 or 0 + for t in targets: - build(t) + if REDO_TARGET: + add_dep(REDO_TARGET, opt.ifcreate and 'c' or 'm', t) + if opt.ifcreate: + pass # just adding the dependency (above) is enough + elif opt.ifchange: + if dirty_deps(t, depth = ''): + build(t) + else: + build(t)