зеркало из https://github.com/mozilla/gecko-dev.git
Bug 873720 - Part 4: Display a build progress indicator; r=ted
This commit is contained in:
Родитель
6c39c3c2ea
Коммит
a5b0db124a
|
@ -7,6 +7,7 @@ from __future__ import print_function, unicode_literals
|
||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from mach.decorators import (
|
from mach.decorators import (
|
||||||
CommandArgument,
|
CommandArgument,
|
||||||
|
@ -14,6 +15,8 @@ from mach.decorators import (
|
||||||
Command,
|
Command,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from mach.mixin.logging import LoggingMixin
|
||||||
|
|
||||||
from mozbuild.base import MachCommandBase
|
from mozbuild.base import MachCommandBase
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +43,209 @@ Preferences.
|
||||||
'''.strip()
|
'''.strip()
|
||||||
|
|
||||||
|
|
||||||
|
class TerminalLoggingHandler(logging.Handler):
|
||||||
|
"""Custom logging handler that works with terminal window dressing.
|
||||||
|
|
||||||
|
This class should probably live elsewhere, like the mach core. Consider
|
||||||
|
this a proving ground for its usefulness.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
logging.Handler.__init__(self)
|
||||||
|
|
||||||
|
self.fh = sys.stdout
|
||||||
|
self.footer = None
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.acquire()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.fh.flush()
|
||||||
|
finally:
|
||||||
|
self.release()
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
msg = self.format(record)
|
||||||
|
|
||||||
|
if self.footer:
|
||||||
|
self.footer.clear()
|
||||||
|
|
||||||
|
self.fh.write(msg)
|
||||||
|
self.fh.write('\n')
|
||||||
|
|
||||||
|
if self.footer:
|
||||||
|
self.footer.draw()
|
||||||
|
|
||||||
|
# If we don't flush, the footer may not get drawn.
|
||||||
|
self.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class BuildProgressFooter(object):
|
||||||
|
"""Handles display of a build progress indicator in a terminal.
|
||||||
|
|
||||||
|
When mach builds inside a blessings-supported terminal, it will render
|
||||||
|
progress information collected from a BuildMonitor. This class converts the
|
||||||
|
state of BuildMonitor into terminal output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, terminal, monitor):
|
||||||
|
# terminal is a blessings.Terminal.
|
||||||
|
self._t = terminal
|
||||||
|
self._fh = sys.stdout
|
||||||
|
self._monitor = monitor
|
||||||
|
|
||||||
|
def _clear_lines(self, n):
|
||||||
|
for i in range(n):
|
||||||
|
self._fh.write(self._t.move_x(0))
|
||||||
|
self._fh.write(self._t.clear_eol())
|
||||||
|
self._fh.write(self._t.move_up())
|
||||||
|
|
||||||
|
self._fh.write(self._t.move_down())
|
||||||
|
self._fh.write(self._t.move_x(0))
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Removes the footer from the current terminal."""
|
||||||
|
self._clear_lines(1)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
"""Draws this footer in the terminal."""
|
||||||
|
if not self._monitor.tiers:
|
||||||
|
return
|
||||||
|
|
||||||
|
# The drawn terminal looks something like:
|
||||||
|
# TIER: base nspr nss js platform app SUBTIER: static export libs tools DIRECTORIES: 06/09 (memory)
|
||||||
|
|
||||||
|
# This is a list of 2-tuples of (encoding function, input). None means
|
||||||
|
# no encoding. For a full reason on why we do things this way, read the
|
||||||
|
# big comment below.
|
||||||
|
parts = [('bold', 'TIER'), ':', ' ']
|
||||||
|
|
||||||
|
current_encountered = False
|
||||||
|
for tier in self._monitor.tiers:
|
||||||
|
if tier == self._monitor.current_tier:
|
||||||
|
parts.extend([('yellow', tier), ' '])
|
||||||
|
current_encountered = True
|
||||||
|
elif not current_encountered:
|
||||||
|
parts.extend([('green', tier), ' '])
|
||||||
|
else:
|
||||||
|
parts.extend([tier, ' '])
|
||||||
|
|
||||||
|
current_encountered = False
|
||||||
|
parts.extend([('bold', 'SUBTIER'), ':', ' '])
|
||||||
|
for subtier in self._monitor.subtiers:
|
||||||
|
if subtier == self._monitor.current_subtier:
|
||||||
|
parts.extend([('yellow', subtier), ' '])
|
||||||
|
current_encountered = True
|
||||||
|
elif not current_encountered:
|
||||||
|
parts.extend([('green', subtier), ' '])
|
||||||
|
else:
|
||||||
|
parts.extend([subtier, ' '])
|
||||||
|
|
||||||
|
if self._monitor.current_subtier_dirs and self._monitor.current_tier_dir:
|
||||||
|
parts.extend([
|
||||||
|
('bold', 'DIRECTORIES'), ': ',
|
||||||
|
'%02d' % self._monitor.current_tier_dir_index,
|
||||||
|
'/',
|
||||||
|
'%02d' % len(self._monitor.current_subtier_dirs),
|
||||||
|
' ',
|
||||||
|
'(', ('magenta', self._monitor.current_tier_dir), ')',
|
||||||
|
])
|
||||||
|
|
||||||
|
# We don't want to write more characters than the current width of the
|
||||||
|
# terminal otherwise wrapping may result in weird behavior. We can't
|
||||||
|
# simply truncate the line at terminal width characters because a)
|
||||||
|
# non-viewable escape characters count towards the limit and b) we
|
||||||
|
# don't want to truncate in the middle of an escape sequence because
|
||||||
|
# subsequent output would inherit the escape sequence.
|
||||||
|
max_width = self._t.width
|
||||||
|
written = 0
|
||||||
|
write_pieces = []
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(part, tuple):
|
||||||
|
func, arg = part
|
||||||
|
|
||||||
|
if written + len(arg) > max_width:
|
||||||
|
write_pieces.append(arg[0:max_width - written])
|
||||||
|
written += len(arg)
|
||||||
|
break
|
||||||
|
|
||||||
|
encoded = getattr(self._t, func)(arg)
|
||||||
|
|
||||||
|
write_pieces.append(encoded)
|
||||||
|
written += len(arg)
|
||||||
|
else:
|
||||||
|
if written + len(part) > max_width:
|
||||||
|
write_pieces.append(arg[0:max_width - written])
|
||||||
|
written += len(part)
|
||||||
|
break
|
||||||
|
|
||||||
|
write_pieces.append(part)
|
||||||
|
written += len(part)
|
||||||
|
|
||||||
|
self._fh.write(''.join(write_pieces))
|
||||||
|
self._fh.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class BuildOutputManager(LoggingMixin):
|
||||||
|
"""Handles writing build output to a terminal, to logs, etc."""
|
||||||
|
|
||||||
|
def __init__(self, log_manager, monitor):
|
||||||
|
self.populate_logger()
|
||||||
|
|
||||||
|
self.monitor = monitor
|
||||||
|
self.footer = None
|
||||||
|
|
||||||
|
terminal = log_manager.terminal
|
||||||
|
|
||||||
|
# TODO convert terminal footer to config file setting.
|
||||||
|
if not terminal or os.environ.get('MACH_NO_TERMINAL_FOOTER', None):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.t = terminal
|
||||||
|
self.footer = BuildProgressFooter(terminal, monitor)
|
||||||
|
|
||||||
|
handler = TerminalLoggingHandler()
|
||||||
|
handler.setFormatter(log_manager.terminal_formatter)
|
||||||
|
handler.footer = self.footer
|
||||||
|
|
||||||
|
old = log_manager.replace_terminal_handler(handler)
|
||||||
|
handler.level = old.level
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
if self.footer:
|
||||||
|
self.footer.clear()
|
||||||
|
|
||||||
|
def write_line(self, line):
|
||||||
|
if self.footer:
|
||||||
|
self.footer.clear()
|
||||||
|
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
if self.footer:
|
||||||
|
self.footer.draw()
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
if not self.footer:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.footer.clear()
|
||||||
|
self.footer.draw()
|
||||||
|
|
||||||
|
def on_line(self, line):
|
||||||
|
warning, state_changed, relevant = self.monitor.on_line(line)
|
||||||
|
|
||||||
|
if warning:
|
||||||
|
self.log(logging.INFO, 'compiler_warning', warning,
|
||||||
|
'Warning: {flag} in {filename}: {message}')
|
||||||
|
|
||||||
|
if relevant:
|
||||||
|
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
||||||
|
elif state_changed:
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
|
||||||
@CommandProvider
|
@CommandProvider
|
||||||
class Build(MachCommandBase):
|
class Build(MachCommandBase):
|
||||||
"""Interface to build the tree."""
|
"""Interface to build the tree."""
|
||||||
|
@ -60,16 +266,7 @@ class Build(MachCommandBase):
|
||||||
warnings_path = self._get_state_filename('warnings.json')
|
warnings_path = self._get_state_filename('warnings.json')
|
||||||
monitor = BuildMonitor(self.topobjdir, warnings_path)
|
monitor = BuildMonitor(self.topobjdir, warnings_path)
|
||||||
|
|
||||||
def on_line(line):
|
with BuildOutputManager(self.log_manager, monitor) as output:
|
||||||
warning, state_changed, relevant = monitor.on_line(line)
|
|
||||||
|
|
||||||
if warning:
|
|
||||||
self.log(logging.INFO, 'compiler_warning', warning,
|
|
||||||
'Warning: {flag} in {filename}: {message}')
|
|
||||||
|
|
||||||
if relevant:
|
|
||||||
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
|
|
||||||
|
|
||||||
monitor.start()
|
monitor.start()
|
||||||
|
|
||||||
if what:
|
if what:
|
||||||
|
@ -111,14 +308,14 @@ class Build(MachCommandBase):
|
||||||
# Build target pairs.
|
# Build target pairs.
|
||||||
for make_dir, make_target in target_pairs:
|
for make_dir, make_target in target_pairs:
|
||||||
status = self._run_make(directory=make_dir, target=make_target,
|
status = self._run_make(directory=make_dir, target=make_target,
|
||||||
line_handler=on_line, log=False, print_directory=False,
|
line_handler=output.on_line, log=False, print_directory=False,
|
||||||
ensure_exit_code=False, num_jobs=jobs, silent=not verbose)
|
ensure_exit_code=False, num_jobs=jobs, silent=not verbose)
|
||||||
|
|
||||||
if status != 0:
|
if status != 0:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
status = self._run_make(srcdir=True, filename='client.mk',
|
status = self._run_make(srcdir=True, filename='client.mk',
|
||||||
line_handler=on_line, log=False, print_directory=False,
|
line_handler=output.on_line, log=False, print_directory=False,
|
||||||
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
|
allow_parallel=False, ensure_exit_code=False, num_jobs=jobs,
|
||||||
silent=not verbose)
|
silent=not verbose)
|
||||||
|
|
||||||
|
@ -127,6 +324,7 @@ class Build(MachCommandBase):
|
||||||
'{count} compiler warnings present.')
|
'{count} compiler warnings present.')
|
||||||
|
|
||||||
monitor.finish()
|
monitor.finish()
|
||||||
|
|
||||||
high_finder, finder_percent = monitor.have_high_finder_usage()
|
high_finder, finder_percent = monitor.have_high_finder_usage()
|
||||||
if high_finder:
|
if high_finder:
|
||||||
print(FINDER_SLOW_MESSAGE % finder_percent)
|
print(FINDER_SLOW_MESSAGE % finder_percent)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче