diff --git a/.pylintrc b/.pylintrc index 59cbbf6..0eae01b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -11,7 +11,7 @@ jobs=1 # We probably want to fix these eventually, but in the meantime, these # ones are relatively harmless. -disable=multiple-imports,missing-docstring,locally-disabled,invalid-name,unused-argument,fixme,global-statement,redefined-variable-type,using-constant-test,unused-variable,file-ignored,simplifiable-if-statement +disable=multiple-imports,locally-disabled,invalid-name,unused-argument,fixme,global-statement,redefined-variable-type,using-constant-test,unused-variable,file-ignored,simplifiable-if-statement [REPORTS] @@ -159,7 +159,7 @@ method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. -no-docstring-rgx=^_ +no-docstring-rgx=^_|^main$ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. diff --git a/redo/atoi.py b/redo/atoi.py index e53ec92..b6a2582 100644 --- a/redo/atoi.py +++ b/redo/atoi.py @@ -1,5 +1,7 @@ +"""Simple integer conversion helper.""" def atoi(v): + """Convert v to an integer, or return 0 on error, like C's atoi().""" try: return int(v or 0) except ValueError: diff --git a/redo/cmd_always.py b/redo/cmd_always.py index 31b7c6a..51fdd14 100644 --- a/redo/cmd_always.py +++ b/redo/cmd_always.py @@ -1,3 +1,4 @@ +"""redo-always: tell redo that the current target is always out of date.""" import sys, os from . import env, logs, state diff --git a/redo/cmd_ifchange.py b/redo/cmd_ifchange.py index b621e2f..eca6516 100644 --- a/redo/cmd_ifchange.py +++ b/redo/cmd_ifchange.py @@ -1,3 +1,4 @@ +"""redo-ifchange: build the given targets if they have changed.""" import os, sys, traceback from . import env, builder, deps, jobserver, logs, state from .logs import debug2, err diff --git a/redo/cmd_ifcreate.py b/redo/cmd_ifcreate.py index 554ba30..86d1a28 100644 --- a/redo/cmd_ifcreate.py +++ b/redo/cmd_ifcreate.py @@ -1,3 +1,4 @@ +"""redo-ifcreate: build the current target if these targets are created.""" import sys, os from . import env, logs, state from .logs import err diff --git a/redo/cmd_log.py b/redo/cmd_log.py index b741dbb..402a5f4 100644 --- a/redo/cmd_log.py +++ b/redo/cmd_log.py @@ -1,3 +1,4 @@ +"""redo-log: print past build logs. """ import errno, fcntl, os, re, struct, sys, time import termios from .atoi import atoi @@ -60,6 +61,10 @@ def _rel(top, mydir, path): def catlog(t): + """Copy the given log content to our current log output device. + + Note: this function's behaviour depends on global command-line options. + """ global total_lines, status if t in already: return diff --git a/redo/cmd_ood.py b/redo/cmd_ood.py index 3a455ce..fec0064 100644 --- a/redo/cmd_ood.py +++ b/redo/cmd_ood.py @@ -1,3 +1,4 @@ +"""redo-ood: list out-of-date (ood) targets.""" import sys, os from . import deps, env, logs, state diff --git a/redo/cmd_redo.py b/redo/cmd_redo.py index 6a0e6f2..88735f0 100644 --- a/redo/cmd_redo.py +++ b/redo/cmd_redo.py @@ -1,3 +1,4 @@ +"""redo: build the listed targets whether they need it or not.""" # # Copyright 2010-2018 Avery Pennarun and contributors # diff --git a/redo/cmd_sources.py b/redo/cmd_sources.py index ffc45d1..b9fba09 100644 --- a/redo/cmd_sources.py +++ b/redo/cmd_sources.py @@ -1,3 +1,4 @@ +"""redo-sources: list the known source (not target) files.""" import sys, os from . import env, logs, state diff --git a/redo/cmd_stamp.py b/redo/cmd_stamp.py index 8de2257..48f12bf 100644 --- a/redo/cmd_stamp.py +++ b/redo/cmd_stamp.py @@ -1,3 +1,4 @@ +"""redo-stamp: tell redo to use a checksum when considering this target.""" import sys, os from . import env, logs, state from .logs import debug2 diff --git a/redo/cmd_targets.py b/redo/cmd_targets.py index 98f2c50..887e202 100644 --- a/redo/cmd_targets.py +++ b/redo/cmd_targets.py @@ -1,3 +1,4 @@ +"""redo-targets: list the known targets (not sources).""" import sys, os from . import env, logs, state diff --git a/redo/cmd_unlocked.py b/redo/cmd_unlocked.py index 4ce4780..81da28e 100644 --- a/redo/cmd_unlocked.py +++ b/redo/cmd_unlocked.py @@ -1,3 +1,4 @@ +"""redo-unlocked: internal tool for building dependencies.""" import sys, os from . import env, logs, state diff --git a/redo/cmd_whichdo.py b/redo/cmd_whichdo.py index 1c63617..ad580c2 100644 --- a/redo/cmd_whichdo.py +++ b/redo/cmd_whichdo.py @@ -1,3 +1,4 @@ +"""redo-whichdo: list the set of .do files considered to build a target.""" import sys, os from . import env, logs, paths from .logs import err diff --git a/redo/deps.py b/redo/deps.py index a1aa6f1..0ce53d3 100644 --- a/redo/deps.py +++ b/redo/deps.py @@ -1,3 +1,4 @@ +"""Code for checking redo target dependencies.""" import os from . import cycles, env, state from .logs import debug @@ -10,6 +11,29 @@ def isdirty(f, depth, max_changed, is_checked=state.File.is_checked, set_checked=state.File.set_checked_save, log_override=state.warn_override): + """Determine if the given state.File needs to be built. + + Args: + f: a state.File representing the target to check. + depth: a string of whitespace representing the recursion depth + (initially '') + max_changed: initially the current runid. If a target is newer than + this, anything that depends on it is considered outdated. + already_checked: initially []. A list of dependencies already + checked in this recursive cycle, to avoid infinite loops. + is_checked: a function that returns whether a given state.File has + already been checked for dirtiness. + set_checked: a function that marks a given state.File as having now + been checked for dirtiness. + log_override: a function that logs a "manual override" warning when + needed. (redo-ood replaces this with a no-op.) + + Returns: + [targets...] if we won't be sure until the given list of targets has + been built. + DIRTY if the given target is definitely dirty. + CLEAN if the given target is definitely not dirty. + """ if f.id in already_checked: raise cycles.CyclicDependencyError() # make a copy of the list, so upon returning, our parent's copy diff --git a/redo/env.py b/redo/env.py index 5724b7d..2d8a338 100644 --- a/redo/env.py +++ b/redo/env.py @@ -1,3 +1,4 @@ +"""Manage redo-related environment variables.""" import os, sys from .atoi import atoi diff --git a/redo/helpers.py b/redo/helpers.py index 64fdf44..70fbfc9 100644 --- a/redo/helpers.py +++ b/redo/helpers.py @@ -1,3 +1,4 @@ +"""Some helper functions that don't fit anywhere else.""" import os, errno, fcntl diff --git a/redo/jobserver.py b/redo/jobserver.py index 46f4cfa..90af08b 100644 --- a/redo/jobserver.py +++ b/redo/jobserver.py @@ -1,5 +1,4 @@ -# -# Implementation of a GNU make-compatible jobserver. +"""Implementation of a GNU make-compatible jobserver.""" # # The basic idea is that both ends of a pipe (tokenfds) are shared with all # subprocesses. At startup, we write one "token" into the pipe for each @@ -201,6 +200,7 @@ def _try_read_all(fd, n): def setup(maxjobs): + """Start the jobserver (if it isn't already) with the given token count.""" global _tokenfds, _cheatfds, _toplevel assert maxjobs > 0 assert not _tokenfds diff --git a/redo/logs.py b/redo/logs.py index ecaf577..3d3a4df 100644 --- a/redo/logs.py +++ b/redo/logs.py @@ -1,3 +1,4 @@ +"""Code for writing log-formatted messages to stderr.""" import os, re, sys, time from . import env @@ -24,6 +25,8 @@ def _check_tty(tty, color): class RawLog(object): + """A log printer for machine-readable logs, suitable for redo-log.""" + def __init__(self, tty): self.file = tty @@ -39,6 +42,8 @@ def write(self, s): class PrettyLog(object): + """A log printer for human-readable logs.""" + def __init__(self, tty): self.topdir = os.getcwd() self.file = tty @@ -53,6 +58,7 @@ def _pretty(self, pid, color, s): BOLD if color else '', s, PLAIN, '\n'])) def write(self, s): + """Write the string 's' to the log.""" assert '\n' not in s sys.stdout.flush() sys.stderr.flush() diff --git a/redo/paths.py b/redo/paths.py index 5600089..a966af2 100644 --- a/redo/paths.py +++ b/redo/paths.py @@ -1,3 +1,4 @@ +"""Code for manipulating file paths.""" import os from . import env from .logs import debug2 @@ -14,6 +15,7 @@ def _default_do_files(filename): def possible_do_files(t): + """Yield a list of tuples describing the .do file needed to build t.""" dirname, filename = os.path.split(t) yield (os.path.join(env.v.BASE, dirname), "%s.do" % filename, '', filename, '') diff --git a/redo/state.py b/redo/state.py index 8e3cdb7..b2270d7 100644 --- a/redo/state.py +++ b/redo/state.py @@ -1,3 +1,4 @@ +"""Code for manipulating redo's state database.""" import sys, os, errno, stat, fcntl, sqlite3 from . import cycles, env from .helpers import unlink, close_on_exec, join @@ -31,6 +32,7 @@ def _connect(dbfile): _db = None def db(): + """Initialize the state database and return its object.""" global _db, _lockfile if _db: return _db @@ -151,6 +153,7 @@ def check_sane(): _cwd = None def relpath(t, base): + """Given a relative or absolute path t, express it relative to base.""" global _cwd if not _cwd: _cwd = os.getcwd() @@ -210,6 +213,8 @@ def warn_override(name): 'checked_runid', 'changed_runid', 'failed_runid', 'stamp', 'csum'] class File(object): + """An object representing a source or target in the redo database.""" + # use this mostly to avoid accidentally assigning to typos __slots__ = ['id'] + _file_cols[1:] @@ -324,6 +329,7 @@ def update_stamp(self, must_exist=False): self.set_changed() def is_source(self): + """Returns true if this object represents a source (not a target).""" if self.name.startswith('//'): return False # special name, ignore newstamp = self.read_stamp() @@ -341,6 +347,7 @@ def is_source(self): return True def is_target(self): + """Returns true if this object represents a target (not a source).""" if not self.is_generated: return False if self.is_source(): @@ -357,6 +364,7 @@ def is_failed(self): return self.failed_runid and self.failed_runid >= env.v.RUNID def deps(self): + """Return the list of objects that this object depends on.""" if self.is_override or not self.is_generated: return q = ('select Deps.mode, Deps.source, %s ' @@ -370,10 +378,21 @@ def deps(self): yield mode, File(cols=cols) def zap_deps1(self): + """Mark the list of dependencies of this object as deprecated. + + We do this when starting a new build of the current target. We don't + delete them right away, because if the build fails, we still want to + know the old deps. + """ debug2('zap-deps1: %r\n' % self.name) _write('update Deps set delete_me=? where target=?', [True, self.id]) def zap_deps2(self): + """Delete any deps that were *not* referenced in the current run. + + Dependencies of a given target can change from one build to the next. + We forget old dependencies only after a build completes successfully. + """ debug2('zap-deps2: %r\n' % self.name) _write('delete from Deps where target=? and delete_me=1', [self.id]) @@ -438,7 +457,10 @@ def logname(fid): # The makes debugging a bit harder. When we someday port to C, we can do that. _locks = {} class Lock(object): + """An object representing a lock on a redo target file.""" + def __init__(self, fid): + """Initialize a lock, given the target's state.File.id.""" self.owned = False self.fid = fid assert _lockfile >= 0 @@ -451,10 +473,12 @@ def __del__(self): self.unlock() def check(self): + """Check that this lock is in a sane state.""" assert not self.owned cycles.check(self.fid) def trylock(self): + """Non-blocking try to acquire our lock; returns true if it worked.""" self.check() assert not self.owned try: @@ -469,6 +493,12 @@ def trylock(self): return self.owned def waitlock(self, shared=False): + """Try to acquire our lock, and wait if it's currently locked. + + If shared=True, acquires a shared lock (which can be shared with + other shared locks; used by redo-log). Otherwise, acquires an + exclusive lock. + """ self.check() assert not self.owned fcntl.lockf( @@ -478,6 +508,7 @@ def waitlock(self, shared=False): self.owned = True def unlock(self): + """Release the lock, which we must currently own.""" if not self.owned: raise Exception("can't unlock %r - we don't own it" % self.fid) diff --git a/redo/title.py b/redo/title.py index 8cdf40d..05ac98b 100644 --- a/redo/title.py +++ b/redo/title.py @@ -1,3 +1,4 @@ +"""Code for manipulating the Unix process title.""" import os, sys # FIXME: setproctitle module is only usable if *not* using python -S, diff --git a/redo/version/__init__.py b/redo/version/__init__.py index 581c2ec..a5e70e8 100644 --- a/redo/version/__init__.py +++ b/redo/version/__init__.py @@ -1 +1,2 @@ +"""A module which provides current redo version information from git.""" from ._version import COMMIT, TAG, DATE diff --git a/redo/version/_version.py.do b/redo/version/_version.py.do index 42dbfce..1edace6 100644 --- a/redo/version/_version.py.do +++ b/redo/version/_version.py.do @@ -1,3 +1,4 @@ redo-ifchange vars +echo '"""Auto-generated file with git version information."""' echo "# pylint: disable=bad-whitespace" cat vars