| # elog/messages.py - elog core functions |
| # Copyright 2006-2011 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import portage |
| portage.proxy.lazyimport.lazyimport(globals(), |
| 'portage.output:colorize', |
| 'portage.util:writemsg', |
| ) |
| |
| from portage.const import EBUILD_PHASES |
| from portage.localization import _ |
| from portage import os |
| from portage import _encodings |
| from portage import _unicode_encode |
| from portage import _unicode_decode |
| |
| import io |
| import sys |
| |
| _log_levels = frozenset([ |
| "ERROR", |
| "INFO", |
| "LOG", |
| "QA", |
| "WARN", |
| ]) |
| |
| def collect_ebuild_messages(path): |
| """ Collect elog messages generated by the bash logging function stored |
| at 'path'. |
| """ |
| mylogfiles = None |
| try: |
| mylogfiles = os.listdir(path) |
| except OSError: |
| pass |
| # shortcut for packages without any messages |
| if not mylogfiles: |
| return {} |
| # exploit listdir() file order so we process log entries in chronological order |
| mylogfiles.reverse() |
| logentries = {} |
| for msgfunction in mylogfiles: |
| filename = os.path.join(path, msgfunction) |
| if msgfunction not in EBUILD_PHASES: |
| writemsg(_("!!! can't process invalid log file: %s\n") % filename, |
| noiselevel=-1) |
| continue |
| if not msgfunction in logentries: |
| logentries[msgfunction] = [] |
| lastmsgtype = None |
| msgcontent = [] |
| f = io.open(_unicode_encode(filename, |
| encoding=_encodings['fs'], errors='strict'), |
| mode='r', encoding=_encodings['repo.content'], errors='replace') |
| # Use split('\n') since normal line iteration or readlines() will |
| # split on \r characters as shown in bug #390833. |
| for l in f.read().split('\n'): |
| if not l: |
| continue |
| try: |
| msgtype, msg = l.split(" ", 1) |
| if msgtype not in _log_levels: |
| raise ValueError(msgtype) |
| except ValueError: |
| writemsg(_("!!! malformed entry in " |
| "log file: '%s': %s\n") % (filename, l), noiselevel=-1) |
| continue |
| |
| if lastmsgtype is None: |
| lastmsgtype = msgtype |
| |
| if msgtype == lastmsgtype: |
| msgcontent.append(msg) |
| else: |
| if msgcontent: |
| logentries[msgfunction].append((lastmsgtype, msgcontent)) |
| msgcontent = [msg] |
| lastmsgtype = msgtype |
| f.close() |
| if msgcontent: |
| logentries[msgfunction].append((lastmsgtype, msgcontent)) |
| |
| # clean logfiles to avoid repetitions |
| for f in mylogfiles: |
| try: |
| os.unlink(os.path.join(path, f)) |
| except OSError: |
| pass |
| return logentries |
| |
| _msgbuffer = {} |
| def _elog_base(level, msg, phase="other", key=None, color=None, out=None): |
| """ Backend for the other messaging functions, should not be called |
| directly. |
| """ |
| |
| # TODO: Have callers pass in a more unique 'key' parameter than a plain |
| # cpv, in order to ensure that messages are properly grouped together |
| # for a given package instance, and also to ensure that each elog module's |
| # process() function is only called once for each unique package. This is |
| # needed not only when building packages in parallel, but also to preserve |
| # continuity in messages when a package is simply updated, since we don't |
| # want the elog_process() call from the uninstall of the old version to |
| # cause discontinuity in the elog messages of the new one being installed. |
| |
| global _msgbuffer |
| |
| if out is None: |
| out = sys.stdout |
| |
| if color is None: |
| color = "GOOD" |
| |
| msg = _unicode_decode(msg, |
| encoding=_encodings['content'], errors='replace') |
| |
| formatted_msg = colorize(color, " * ") + msg + "\n" |
| |
| # avoid potential UnicodeEncodeError |
| if out in (sys.stdout, sys.stderr): |
| formatted_msg = _unicode_encode(formatted_msg, |
| encoding=_encodings['stdio'], errors='backslashreplace') |
| if sys.hexversion >= 0x3000000: |
| out = out.buffer |
| |
| out.write(formatted_msg) |
| |
| if key not in _msgbuffer: |
| _msgbuffer[key] = {} |
| if phase not in _msgbuffer[key]: |
| _msgbuffer[key][phase] = [] |
| _msgbuffer[key][phase].append((level, msg)) |
| |
| #raise NotImplementedError() |
| |
| def collect_messages(key=None, phasefilter=None): |
| global _msgbuffer |
| |
| if key is None: |
| rValue = _msgbuffer |
| _reset_buffer() |
| else: |
| rValue = {} |
| if key in _msgbuffer: |
| if phasefilter is None: |
| rValue[key] = _msgbuffer.pop(key) |
| else: |
| rValue[key] = {} |
| for phase in phasefilter: |
| try: |
| rValue[key][phase] = _msgbuffer[key].pop(phase) |
| except KeyError: |
| pass |
| if not _msgbuffer[key]: |
| del _msgbuffer[key] |
| return rValue |
| |
| def _reset_buffer(): |
| """ Reset the internal message buffer when it has been processed, |
| should not be called directly. |
| """ |
| global _msgbuffer |
| |
| _msgbuffer = {} |
| |
| # creating and exporting the actual messaging functions |
| _functions = { "einfo": ("INFO", "GOOD"), |
| "elog": ("LOG", "GOOD"), |
| "ewarn": ("WARN", "WARN"), |
| "eqawarn": ("QA", "WARN"), |
| "eerror": ("ERROR", "BAD"), |
| } |
| |
| class _make_msgfunction(object): |
| __slots__ = ('_color', '_level') |
| def __init__(self, level, color): |
| self._level = level |
| self._color = color |
| def __call__(self, msg, phase="other", key=None, out=None): |
| """ |
| Display and log a message assigned to the given key/cpv. |
| """ |
| _elog_base(self._level, msg, phase=phase, |
| key=key, color=self._color, out=out) |
| |
| for f in _functions: |
| setattr(sys.modules[__name__], f, _make_msgfunction(_functions[f][0], _functions[f][1])) |
| del f, _functions |