It's time to start preparing for a version of redo that doesn't work unless we build it first (because it will rely on C modules, and eventually be rewritten in C altogether). To get rolling, remove the old-style symlinks to the main programs, and rename those programs from redo-*.py to redo/cmd_*.py. We'll also move all library functions into the redo/ dir, which is a more python-style naming convention. Previously, install.do was generating wrappers for installing in /usr/bin, which extend sys.path and then import+run the right file. This made "installed" redo work quite differently from running redo inside its source tree. Instead, let's always generate the wrappers in bin/, and not make anything executable except those wrappers. Since we're generating wrappers anyway, let's actually auto-detect the right version of python for the running system; distros can't seem to agree on what to call their python2 binaries (sigh). We'll fill in the right #! shebang lines. Since we're doing that, we can stop using /usr/bin/env, which will a) make things slightly faster, and b) let us use "python -S", which tells python not to load a bunch of extra crap we're not using, thus improving startup times. Annoyingly, we now have to build redo using minimal/do, then run the tests using bin/redo. To make this less annoying, we add a toplevel ./do script that knows the right steps, and a Makefile (whee!) for people who are used to typing 'make' and 'make test' and 'make clean'.
154 lines
4.2 KiB
Python
154 lines
4.2 KiB
Python
import os, re, sys, time
|
|
import vars
|
|
|
|
RED = GREEN = YELLOW = BOLD = PLAIN = None
|
|
|
|
|
|
def check_tty(file, color):
|
|
global RED, GREEN, YELLOW, BOLD, PLAIN
|
|
color_ok = file.isatty() and (os.environ.get('TERM') or 'dumb') != 'dumb'
|
|
if (color and color_ok) or color >= 2:
|
|
# ...use ANSI formatting codes.
|
|
# pylint: disable=bad-whitespace
|
|
RED = "\x1b[31m"
|
|
GREEN = "\x1b[32m"
|
|
YELLOW = "\x1b[33m"
|
|
BOLD = "\x1b[1m"
|
|
PLAIN = "\x1b[m"
|
|
else:
|
|
RED = ""
|
|
GREEN = ""
|
|
YELLOW = ""
|
|
BOLD = ""
|
|
PLAIN = ""
|
|
|
|
|
|
class RawLog(object):
|
|
def __init__(self, file):
|
|
self.file = file
|
|
|
|
def write(self, s):
|
|
assert '\n' not in s
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
self.file.write(s + '\n')
|
|
self.file.flush()
|
|
|
|
|
|
REDO_RE = re.compile(r'@@REDO:([^@]+)@@ (.*)$')
|
|
|
|
|
|
class PrettyLog(object):
|
|
def __init__(self, file):
|
|
self.topdir = os.getcwd()
|
|
self.file = file
|
|
|
|
def _pretty(self, pid, color, s):
|
|
if vars.DEBUG_PIDS:
|
|
redo = '%-6d redo ' % pid
|
|
else:
|
|
redo = 'redo '
|
|
self.file.write(
|
|
''.join([color, redo, vars.DEPTH,
|
|
BOLD if color else '', s, PLAIN, '\n']))
|
|
|
|
def write(self, s):
|
|
assert '\n' not in s
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
g = REDO_RE.match(s)
|
|
if g:
|
|
all = g.group(0)
|
|
self.file.write(s[:-len(all)])
|
|
words = g.group(1).split(':')
|
|
text = g.group(2)
|
|
kind, pid, _ = words[0:3]
|
|
pid = int(pid)
|
|
if kind == 'unchanged':
|
|
self._pretty(pid, '', '%s (unchanged)' % text)
|
|
elif kind == 'check':
|
|
self._pretty(pid, GREEN, '(%s)' % text)
|
|
elif kind == 'do':
|
|
self._pretty(pid, GREEN, text)
|
|
elif kind == 'done':
|
|
rv, name = text.split(' ', 1)
|
|
rv = int(rv)
|
|
if rv:
|
|
self._pretty(pid, RED, '%s (exit %d)' % (name, rv))
|
|
elif vars.VERBOSE or vars.XTRACE or vars.DEBUG:
|
|
self._pretty(pid, GREEN, '%s (done)' % name)
|
|
self.file.write('\n')
|
|
elif kind == 'locked':
|
|
if vars.DEBUG_LOCKS:
|
|
self._pretty(pid, GREEN, '%s (locked...)' % text)
|
|
elif kind == 'waiting':
|
|
if vars.DEBUG_LOCKS:
|
|
self._pretty(pid, GREEN, '%s (WAITING)' % text)
|
|
elif kind == 'unlocked':
|
|
if vars.DEBUG_LOCKS:
|
|
self._pretty(pid, GREEN, '%s (...unlocked!)' % text)
|
|
elif kind == 'error':
|
|
self.file.write(''.join([RED, 'redo: ',
|
|
BOLD, text, PLAIN, '\n']))
|
|
elif kind == 'warning':
|
|
self.file.write(''.join([YELLOW, 'redo: ',
|
|
BOLD, text, PLAIN, '\n']))
|
|
elif kind == 'debug':
|
|
self._pretty(pid, '', text)
|
|
else:
|
|
assert 0, 'Unexpected @@REDO kind: %r' % kind
|
|
else:
|
|
self.file.write(s + '\n')
|
|
self.file.flush()
|
|
|
|
|
|
_log = None
|
|
|
|
def setup(file, pretty, color):
|
|
global _log
|
|
if pretty or vars.PRETTY:
|
|
check_tty(file, color=color)
|
|
_log = PrettyLog(file=file)
|
|
else:
|
|
_log = RawLog(file=file)
|
|
|
|
|
|
# FIXME: explicitly initialize in each program, for clarity
|
|
setup(file=sys.stderr, pretty=vars.PRETTY, color=vars.COLOR)
|
|
|
|
|
|
def write(s):
|
|
_log.write(s)
|
|
|
|
|
|
def meta(kind, s, pid=None):
|
|
assert ':' not in kind
|
|
assert '@' not in kind
|
|
assert '\n' not in s
|
|
if pid is None:
|
|
pid = os.getpid()
|
|
write('@@REDO:%s:%d:%.4f@@ %s'
|
|
% (kind, pid, time.time(), s))
|
|
|
|
def err(s):
|
|
s = s.rstrip()
|
|
meta('error', s)
|
|
|
|
def warn(s):
|
|
s = s.rstrip()
|
|
meta('warning', s)
|
|
|
|
def debug(s):
|
|
if vars.DEBUG >= 1:
|
|
s = s.rstrip()
|
|
meta('debug', s)
|
|
|
|
def debug2(s):
|
|
if vars.DEBUG >= 2:
|
|
s = s.rstrip()
|
|
meta('debug', s)
|
|
|
|
def debug3(s):
|
|
if vars.DEBUG >= 3:
|
|
s = s.rstrip()
|
|
meta('debug', s)
|