diff --git a/config/bootwrapper.yaml b/config/bootwrapper.yaml index f1f7e3036bbb7386f0133a10ea6571a47641cf12..3cb0c76a39d30be7ff3e6579d8d423ef7ec799c8 100644 --- a/config/bootwrapper.yaml +++ b/config/bootwrapper.yaml @@ -36,5 +36,6 @@ run: terminals: bp.terminal_0: - friendly: linux + friendly: '' type: stdinout + no_color: true diff --git a/config/cca-3world.yaml b/config/cca-3world.yaml index 079558c28f19b9d386d74a5215bf46e6dfeabbdc..9d4515cda95d6309c5b34df099a7a7e8836fb6d5 100644 --- a/config/cca-3world.yaml +++ b/config/cca-3world.yaml @@ -114,8 +114,9 @@ run: terminals: bp.terminal_0: - friendly: tfa+linux + friendly: '' type: stdinout + no_color: true bp.terminal_1: friendly: tfa-rt diff --git a/config/ns-edk2-acpi.yaml b/config/ns-edk2-acpi.yaml index fc68c99ba8374a217462b8963f05b7246170fa4b..2200739f03a9104c146e1b7075041a9e519bb308 100644 --- a/config/ns-edk2-acpi.yaml +++ b/config/ns-edk2-acpi.yaml @@ -92,7 +92,9 @@ run: terminals: bp.terminal_0: - friendly: tfa+edk2+linux + friendly: '' type: stdinout + no_color: true + no_escapes: 'EFI stub: Booting Linux Kernel...' bp.terminal_1: friendly: edk2 diff --git a/config/ns-preload.yaml b/config/ns-preload.yaml index ea1ae8ca02c907c66bed1ac5c692522d6ce616c6..8505a6b72fc2935749cf4bfe2e4f3cf563c6df5f 100644 --- a/config/ns-preload.yaml +++ b/config/ns-preload.yaml @@ -79,5 +79,6 @@ run: terminals: bp.terminal_0: - friendly: tfa+linux + friendly: '' type: stdinout + no_color: true diff --git a/documentation/userguide/config.rst b/documentation/userguide/config.rst index c750190b4b96483056f5e376dc6fbcb0ae0ad81b..c75c4dfda6f280a930515d7cc199b276d908b5a5 100644 --- a/documentation/userguide/config.rst +++ b/documentation/userguide/config.rst @@ -206,9 +206,11 @@ terminal section =========== =========== =========== key type description =========== =========== =========== -friendly string Label to display against the terminal when muxing to stdout. +friendly string Label to display against the terminal when muxing to stdout. An empty string disables the prefix for the output. port_regex string Regex to use to find the TCP port of the terminal when parsing the FVP stdout. Must have single capture group. type enum-string Terminal type. See below for options. +no_color boolean Optional (defaults to false, only applies to ['stdout', 'stdinout'] types): If true, output from this terminal is not color-coded. If this terminal carries the interactive shell, it is advised to set this to true to prevent interferring with the shell's escape sequences. --no-color command line option causes this to behave as if set to true. +no_escapes bool/string Optional (defaults to false, only applies to ['stdout', 'stdinout'] types): If true, strips any escape sequences from the output stream before forwarding to the terminal. If a string, behaves as if true until the string is found in the output, which sets it to false. Useful to expunge escape sequences from EDK2 during boot. =========== =========== =========== Terminal types: diff --git a/shrinkwrap/commands/run.py b/shrinkwrap/commands/run.py index e6561d746359e265adf382b5425a8ec863c1ae86..92f4e71cf09e1dffb2d9bad16443cedba82f7357 100644 --- a/shrinkwrap/commands/run.py +++ b/shrinkwrap/commands/run.py @@ -99,7 +99,7 @@ def dispatch(args): if len(t['friendly']) > name_field: name_field = min(len(t['friendly']), max_name_field) - log = logger.Logger(name_field, not args.no_color) + log = logger.Logger(name_field) def _strip_telnet_header(pm, proc, data, streamid): """ @@ -121,6 +121,20 @@ def dispatch(args): else: log.log(pm, proc, line, streamid) + def _colorize(global_no_color, terminal): + if global_no_color: + return False + elif 'no_color' in terminal: + return not terminal['no_color'] + else: + return True + + def _escape(terminal): + if 'no_escapes' in terminal: + return terminal['no_escapes'] + else: + return False + def _find_term_ports(pm, proc, data, streamid): """ Initial handler function called by ProcessManager. When the fvp @@ -155,19 +169,21 @@ def dispatch(args): name = t['friendly'] type = t['type'] port = t["port"] + colorize = _colorize(args.no_color, t) + escape = _escape(t) if type in ['stdout']: cmd = f'nc localhost {port}' pm.add(process.Process(cmd, - False, - (log.alloc_data(name), k), - False)) + False, + (log.alloc_data(name, colorize, escape), k), + False)) if type in ['stdinout']: cmd = f'telnet localhost {port}' pm.add(process.Process(cmd, - True, - (log.alloc_data(name), k), - False)) + True, + (log.alloc_data(name, colorize, escape), k), + False)) t['strip'] = True strip = True if type in ['xterm']: @@ -221,9 +237,9 @@ def dispatch(args): # the fvp has gone. pm = process.ProcessManager(_find_term_ports, _complete) pm.add(process.Process(f'bash {tmpfilename}', - False, - (log.alloc_data('fvp'),), - True)) + False, + (log.alloc_data('fvp', not args.no_color),), + True)) rt_ip = runtime.get().ip_address() diff --git a/shrinkwrap/utils/graph.py b/shrinkwrap/utils/graph.py index 297ba20df5478ff88bc4b4d580f2a713d4824fb6..b80f4fcbc1e2e8f70e3a7734f8eae068711759f5 100644 --- a/shrinkwrap/utils/graph.py +++ b/shrinkwrap/utils/graph.py @@ -113,7 +113,7 @@ def execute(graph, tasks, verbose=False, colorize=True): queue = [] active = 0 - log = logger.Logger(27, colorize) + log = logger.Logger(27) ts = graphlib.TopologicalSorter(graph) def _pump(pm): @@ -127,7 +127,7 @@ def execute(graph, tasks, verbose=False, colorize=True): frag.config, frag.component, frag.summary + '...') - data = (log.alloc_data(str(frag)), []) + data = (log.alloc_data(str(frag), colorize), []) _run_script(pm, data, frag) active += 1 diff --git a/shrinkwrap/utils/logger.py b/shrinkwrap/utils/logger.py index c9e63db1f7ec9d4c09ae12fa850032f56ac4ec00..233c4c254a211364c28ad7770ba67486f715b298 100644 --- a/shrinkwrap/utils/logger.py +++ b/shrinkwrap/utils/logger.py @@ -6,9 +6,28 @@ from collections import namedtuple import re termcolor = None + +def _import_termcolor(): + global termcolor + import termcolor as tc + termcolor = tc + + _ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') _colors = ['blue', 'cyan', 'green', 'yellow', 'magenta'] -Data = namedtuple("Data", "tag color") +Data = namedtuple("Data", "id tag color noesc escbuf") + + +class MatchBuf: + def __init__(self, match): + self._match = match + self._buf = '' + + def match(self, data): + self._buf += data + found = self._buf.find(self._match) >= 0 + self._buf = self._buf[1 - len(self._match):] + return found def splitlines(string): @@ -23,29 +42,38 @@ def splitlines(string): class Logger: - def __init__(self, tag_size, colorize): + def __init__(self, tag_size): self._tag_size = tag_size - self._colorize = colorize + self._id_next = 0 self._color_next = 0 - self._prev_tag = None + self._prev_id = None self._prev_char = '\n' - if self._colorize: - global termcolor - import termcolor as tc - termcolor = tc - - def alloc_data(self, tag): + def alloc_data(self, tag, colorize, no_escapes=False): """ Returns the object that should be stashed in proc.data[0] when log() is called. Includes the tag for the process and an allocated colour. """ - idx = self._color_next - self._color_next += 1 - self._color_next %= len(_colors) - color = _colors[idx] - return Data(tag, color) + if colorize: + _import_termcolor() + color = self._color_next + self._color_next += 1 + color = _colors[color % len(_colors)] + else: + color = None + + id = self._id_next + self._id_next += 1 + + if type(no_escapes) == str: + noesc = True + escbuf = MatchBuf(no_escapes) + else: + noesc = no_escapes + escbuf = None + + return Data(id, tag, color, [noesc], escbuf) def log(self, pm, proc, data, streamid): """ @@ -53,21 +81,26 @@ class Logger: terminals) to the terminal. Text is colored and a tag is added on the left to identify the originating process. """ - # Remove any ansi escape sequences since we are just outputting - # text to stdout. This defends against EDK2's agregious use of - # screen clearing. But it does have the side effect that - # legitimate shell usage can get a bit wonky. - data = _ansi_escape.sub('', data) - if len(data) == 0: - return - + id = proc.data[0].id tag = proc.data[0].tag color = proc.data[0].color + noesc = proc.data[0].noesc + escbuf = proc.data[0].escbuf + + # Remove any ansi escape sequences if requested. + if noesc[0]: + if escbuf and escbuf.match(data): + noesc[0] = False + else: + data = _ansi_escape.sub('', data) + if len(data) == 0: + return # Make the tag. if len(tag) > self._tag_size: tag = tag[:self._tag_size-3] + '...' - tag = f'{{:>{self._tag_size}}}'.format(tag) + if len(tag): + tag = f'{{:>{self._tag_size}}}'.format(tag) lines = splitlines(data) start = 0 @@ -78,7 +111,7 @@ class Logger: # the first part of the line has a different owner, insert a # newline and add a tag for the new owner. if self._prev_char != '\n': - if self._prev_tag == tag: + if self._prev_id == id: self.print(lines[0], tag, True, color, end='') start = 1 else: @@ -87,14 +120,15 @@ class Logger: for line in lines[start:]: self.print(line, tag, False, color, end='') - self._prev_tag = tag + self._prev_id = id self._prev_char = lines[-1][-1] sys.stdout.flush() def print(self, text, tag, cont, color=None, on_color=None, attrs=None, **kwargs): # Ensure that any '\r's only rewind to the end of the tag. - tag = f'[ {tag} ] ' + if len(tag): + tag = f'[ {tag} ] ' text = text.replace('\r', f'\r{tag}') if not cont: @@ -103,6 +137,6 @@ class Logger: self._print(text, color, on_color, attrs, **kwargs) def _print(self, text, color=None, on_color=None, attrs=None, **kwargs): - if self._colorize: + if color: text = termcolor.colored(text, color, on_color, attrs) print(text, **kwargs)