diff --git a/redo/builder.py b/redo/builder.py index a3ee8dd..22f77c5 100644 --- a/redo/builder.py +++ b/redo/builder.py @@ -1,5 +1,5 @@ import sys, os, errno, stat, signal, time -import env, jobserver, state, paths +import cycles, env, jobserver, state, paths from helpers import unlink, close_on_exec import logs from logs import debug2, err, warn, meta, check_tty @@ -129,7 +129,7 @@ class BuildJob(object): try: try: is_target, dirty = self.shouldbuildfunc(self.t) - except state.CyclicDependencyError: + except cycles.CyclicDependencyError: err('cyclic dependency while checking %s\n' % _nice(self.t)) raise ImmediateReturn(208) if not dirty: @@ -264,7 +264,7 @@ class BuildJob(object): os.environ['REDO_PWD'] = state.relpath(newp, env.STARTDIR) os.environ['REDO_TARGET'] = self.basename + self.ext os.environ['REDO_DEPTH'] = env.DEPTH + ' ' - env.add_lock(str(self.lock.fid)) + cycles.add(self.lock.fid) if dn: os.chdir(dn) os.dup2(self.f.fileno(), 1) @@ -499,7 +499,7 @@ def main(targets, shouldbuildfunc): meta('waiting', state.target_relpath(t)) try: lock.check() - except state.CyclicDependencyError: + except cycles.CyclicDependencyError: err('cyclic dependency while building %s\n' % _nice(t)) retcode[0] = 208 return retcode[0] diff --git a/redo/cycles.py b/redo/cycles.py new file mode 100644 index 0000000..8decef4 --- /dev/null +++ b/redo/cycles.py @@ -0,0 +1,24 @@ +"""Code for detecting and aborting on cyclic dependency loops.""" +import os + + +class CyclicDependencyError(Exception): + pass + + +def _get(): + """Get the list of held cycle items.""" + return os.environ.get('REDO_CYCLES', '').split(':') + + +def add(fid): + """Add a lock to the list of held cycle items.""" + items = set(_get()) + items.add(str(fid)) + os.environ['REDO_CYCLES'] = ':'.join(list(items)) + + +def check(fid): + if str(fid) in _get(): + # Lock already held by parent: cyclic dependency + raise CyclicDependencyError() diff --git a/redo/deps.py b/redo/deps.py index 00165e4..d96addf 100644 --- a/redo/deps.py +++ b/redo/deps.py @@ -1,5 +1,5 @@ import os -import env, state +import cycles, env, state from logs import debug CLEAN = 0 @@ -11,7 +11,7 @@ def isdirty(f, depth, max_changed, set_checked=state.File.set_checked_save, log_override=state.warn_override): if f.id in already_checked: - raise state.CyclicDependencyError() + raise cycles.CyclicDependencyError() # make a copy of the list, so upon returning, our parent's copy # is unaffected already_checked = list(already_checked) + [f.id] diff --git a/redo/env.py b/redo/env.py index fa2a32c..a0e0123 100644 --- a/redo/env.py +++ b/redo/env.py @@ -33,14 +33,3 @@ os.environ['REDO_UNLOCKED'] = '' # not inheritable by subprocesses NO_OOB = os.environ.get('REDO_NO_OOB', '') and 1 or 0 os.environ['REDO_NO_OOB'] = '' # not inheritable by subprocesses - - -def get_locks(): - """Get the list of held locks.""" - return os.environ.get('REDO_LOCKS', '').split(':') - -def add_lock(name): - """Add a lock to the list of held locks.""" - locks = set(get_locks()) - locks.add(name) - os.environ['REDO_LOCKS'] = ':'.join(list(locks)) diff --git a/redo/state.py b/redo/state.py index c647376..a51bae2 100644 --- a/redo/state.py +++ b/redo/state.py @@ -1,5 +1,5 @@ import sys, os, errno, stat, fcntl, sqlite3 -import env +import cycles, env from helpers import unlink, close_on_exec, join from logs import warn, debug2, debug3 @@ -24,10 +24,6 @@ STAMP_MISSING = '0' # the stamp of a nonexistent file LOG_LOCK_MAGIC = 0x10000000 # fid offset for "log locks" -class CyclicDependencyError(Exception): - pass - - def _connect(dbfile): _db = sqlite3.connect(dbfile, timeout=TIMEOUT) _db.execute("pragma synchronous = off") @@ -466,9 +462,7 @@ class Lock(object): def check(self): assert not self.owned - if str(self.fid) in env.get_locks(): - # Lock already held by parent: cyclic dependence - raise CyclicDependencyError() + cycles.check(self.fid) def trylock(self): self.check()