This lets us pack more onto on line if the terminal is wider, and operate correctly if it's narrower.
196 lines
6.3 KiB
Python
Executable file
196 lines
6.3 KiB
Python
Executable file
#!/usr/bin/env python
|
|
import errno, fcntl, os, re, struct, sys, termios, time
|
|
import options
|
|
|
|
optspec = """
|
|
redo-log [options...] [targets...]
|
|
--
|
|
r,recursive show build logs for dependencies too
|
|
u,unchanged show lines for dependencies not needing to be rebuilt
|
|
f,follow keep watching for more lines to be appended (like tail -f)
|
|
no-details only show 'redo' recursion trace, not build output
|
|
no-colorize don't colorize 'redo' log messages
|
|
no-status don't display build summary line in --follow
|
|
ack-fd= (internal use only) print REDO-OK to this fd upon starting
|
|
"""
|
|
o = options.Options(optspec)
|
|
(opt, flags, extra) = o.parse(sys.argv[1:])
|
|
targets = extra
|
|
|
|
import vars_init
|
|
vars_init.init(list(targets))
|
|
|
|
import vars, state
|
|
|
|
already = set()
|
|
queue = []
|
|
depth = []
|
|
total_lines = 0
|
|
status = None
|
|
|
|
|
|
# regexp for matching "redo" lines in the log, which we use for recursion.
|
|
# format:
|
|
# redo path/to/target which might have spaces
|
|
# redo [unchanged] path/to/target which might have spaces
|
|
# redo path/to/target which might have spaces (comment)
|
|
# FIXME: use a more structured format when writing the logs.
|
|
# That will prevent false positives and negatives. Then transform the
|
|
# structured format into a user-friendly format when printing in redo-log.
|
|
REDO_LINE_RE = re.compile(r'^redo\s+(\[\w+\] )?([^(:]+)( \([^)]+\))?\n$')
|
|
|
|
|
|
def _tty_width():
|
|
s = struct.pack("HHHH", 0, 0, 0, 0)
|
|
try:
|
|
import fcntl, termios
|
|
s = fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, s)
|
|
except (IOError, ImportError):
|
|
return _atoi(os.environ.get('WIDTH')) or 70
|
|
(ysize,xsize,ypix,xpix) = struct.unpack('HHHH', s)
|
|
return xsize or 70
|
|
|
|
|
|
def is_locked(fid):
|
|
return (fid is not None) and not state.Lock(fid=fid).trylock()
|
|
|
|
|
|
def catlog(t):
|
|
global total_lines, status
|
|
if t in already:
|
|
return
|
|
depth.append(t)
|
|
already.add(t)
|
|
if t == '-':
|
|
f = sys.stdin
|
|
fid = None
|
|
logname = None
|
|
else:
|
|
try:
|
|
sf = state.File(name=t, allow_add=False)
|
|
except KeyError:
|
|
sys.stderr.write('redo-log: %r: not known to redo.\n' % (t,))
|
|
sys.exit(24)
|
|
fid = sf.id
|
|
del sf
|
|
state.rollback()
|
|
logname = state.logname(fid)
|
|
f = None
|
|
delay = 0.01
|
|
was_locked = is_locked(fid)
|
|
line_head = ''
|
|
width = _tty_width()
|
|
while 1:
|
|
if not f:
|
|
try:
|
|
f = open(logname)
|
|
except IOError, e:
|
|
if e.errno == errno.ENOENT:
|
|
# ignore files without logs
|
|
pass
|
|
else:
|
|
raise
|
|
if f:
|
|
# Note: normally includes trailing \n.
|
|
# In 'follow' mode, might get a line with no trailing \n
|
|
# (eg. when ./configure is halfway through a test), which we
|
|
# deal with below.
|
|
line = f.readline()
|
|
else:
|
|
line = None
|
|
if not line and (not opt.follow or not was_locked):
|
|
# file not locked, and no new lines: done
|
|
break
|
|
if not line:
|
|
was_locked = is_locked(fid)
|
|
if opt.follow:
|
|
if opt.status:
|
|
width = _tty_width()
|
|
head = 'redo %s ' % ('{:,}'.format(total_lines))
|
|
tail = ''
|
|
for n in reversed(depth):
|
|
remain = width - len(head) - len(tail)
|
|
# always leave room for a final '... ' prefix
|
|
if remain < len(n) + 4 + 1 or remain <= 4:
|
|
if len(n) < 6 or remain < 6 + 1 + 4:
|
|
tail = '... %s' % tail
|
|
else:
|
|
start = len(n) - (remain - 3 - 1)
|
|
tail = '...%s %s' % (n[start:], tail)
|
|
break
|
|
elif n != '-':
|
|
tail = n + ' ' + tail
|
|
status = head + tail
|
|
if len(status) > width:
|
|
sys.stderr.write('\nOVERSIZE STATUS (%d):\n%r\n' %
|
|
(len(status), status))
|
|
assert(len(status) <= width)
|
|
sys.stdout.flush()
|
|
sys.stderr.write('\r%-*.*s\r' % (width, width, status))
|
|
time.sleep(min(delay, 1.0))
|
|
delay += 0.01
|
|
continue
|
|
total_lines += 1
|
|
delay = 0.01
|
|
if not line.endswith('\n'):
|
|
line_head += line
|
|
continue
|
|
if line_head:
|
|
line = line_head + line
|
|
line_head = ''
|
|
if status:
|
|
sys.stdout.flush()
|
|
sys.stderr.write('\r%-*.*s\r' % (width, width, ''))
|
|
status = None
|
|
g = re.match(REDO_LINE_RE, line)
|
|
if g:
|
|
attr, name, comment = g.groups()
|
|
if attr == '[unchanged] ':
|
|
if opt.unchanged:
|
|
if name not in already:
|
|
sys.stdout.write(line)
|
|
if opt.recursive:
|
|
catlog(name)
|
|
else:
|
|
sys.stdout.write(line)
|
|
if opt.recursive and (not comment or comment == ' (WAITING)'):
|
|
assert name
|
|
catlog(name)
|
|
else:
|
|
if opt.details:
|
|
sys.stdout.write(line)
|
|
if status:
|
|
sys.stdout.flush()
|
|
sys.stderr.write('\r%-*.*s\r' % (width, width, ''))
|
|
status = None
|
|
if line_head:
|
|
# partial line never got terminated
|
|
print line_head
|
|
assert(depth[-1] == t)
|
|
depth.pop(-1)
|
|
|
|
try:
|
|
if not targets:
|
|
sys.stderr.write('redo-log: give at least one target; maybe "all"?\n')
|
|
sys.exit(1)
|
|
if opt.status < 2 and not os.isatty(2):
|
|
opt.status = False
|
|
if opt.ack_fd:
|
|
ack_fd = int(opt.ack_fd)
|
|
assert(ack_fd > 2)
|
|
if os.write(ack_fd, 'REDO-OK\n') != 8:
|
|
raise Exception('write to ack_fd returned wrong length')
|
|
os.close(ack_fd)
|
|
queue += targets
|
|
while queue:
|
|
t = queue.pop(0)
|
|
if t != '-':
|
|
print 'redo %s' % t
|
|
catlog(t)
|
|
except KeyboardInterrupt:
|
|
sys.exit(200)
|
|
except IOError, e:
|
|
if e.errno == errno.EPIPE:
|
|
pass
|
|
else:
|
|
raise
|