Gentoo Archives: gentoo-portage-dev

From: Brian Dolbec <dolsen@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] [PATCH 2/2] Support PORTAGE_LOG_FILTER_FILE_CMD (bug 709746)
Date: Mon, 22 Jun 2020 14:46:11
Message-Id: 20200622074606.3fc9796f@storm
In Reply to: [gentoo-portage-dev] [PATCH 2/2] Support PORTAGE_LOG_FILTER_FILE_CMD (bug 709746) by Zac Medico
1 On Fri, 19 Jun 2020 13:39:19 -0700
2 Zac Medico <zmedico@g.o> wrote:
3
4 > This variable specifies a command that filters build log output to a
5 > log file. The plan is to extend this to support a separate filter for
6 > tty output in the future.
7 >
8 > In order to enable the EbuildPhase class to write elog messages to
9 > the build log with PORTAGE_LOG_FILTER_FILE_CMD support, convert its
10 > _elog method to a coroutine, and add a SchedulerInterface async_output
11 > method for it to use.
12 >
13 > Use a new BuildLogger class to manage log output (with or without a
14 > filter command), with compression support provided by PipeLogger.
15 > BuildLogger has a stdin property which provides access to a writable
16 > binary file stream (refers to a pipe) that log content is written to.
17 >
18 > Bug: https://bugs.gentoo.org/709746
19 > Signed-off-by: Zac Medico <zmedico@g.o>
20 > ---
21 > lib/_emerge/AbstractEbuildProcess.py | 3 +-
22 > lib/_emerge/BinpkgFetcher.py | 3 +-
23 > lib/_emerge/EbuildFetcher.py | 3 +-
24 > lib/_emerge/EbuildPhase.py | 47 ++++++--
25 > lib/_emerge/SpawnProcess.py | 58 +++++++---
26 > lib/portage/dbapi/_MergeProcess.py | 3 +-
27 > .../ebuild/_config/special_env_vars.py | 8 +-
28 > lib/portage/util/_async/BuildLogger.py | 109
29 > ++++++++++++++++++ lib/portage/util/_async/SchedulerInterface.py |
30 > 32 ++++- man/make.conf.5 | 7 +-
31 > 10 files changed, 243 insertions(+), 30 deletions(-)
32 > create mode 100644 lib/portage/util/_async/BuildLogger.py
33 >
34 > diff --git a/lib/_emerge/AbstractEbuildProcess.py
35 > b/lib/_emerge/AbstractEbuildProcess.py index 1c1955cfe..ae1aae55f
36 > 100644 --- a/lib/_emerge/AbstractEbuildProcess.py
37 > +++ b/lib/_emerge/AbstractEbuildProcess.py
38 > @@ -1,4 +1,4 @@
39 > -# Copyright 1999-2019 Gentoo Foundation
40 > +# Copyright 1999-2020 Gentoo Authors
41 > # Distributed under the terms of the GNU General Public License v2
42 >
43 > import errno
44 > @@ -196,6 +196,7 @@ class AbstractEbuildProcess(SpawnProcess):
45 > null_fd = os.open('/dev/null', os.O_RDONLY)
46 > self.fd_pipes[0] = null_fd
47 >
48 > + self.log_filter_file =
49 > self.settings.get('PORTAGE_LOG_FILTER_FILE_CMD') try:
50 > SpawnProcess._start(self)
51 > finally:
52 > diff --git a/lib/_emerge/BinpkgFetcher.py
53 > b/lib/_emerge/BinpkgFetcher.py index 36d027de3..2e5861cc1 100644
54 > --- a/lib/_emerge/BinpkgFetcher.py
55 > +++ b/lib/_emerge/BinpkgFetcher.py
56 > @@ -1,4 +1,4 @@
57 > -# Copyright 1999-2018 Gentoo Foundation
58 > +# Copyright 1999-2020 Gentoo Authors
59 > # Distributed under the terms of the GNU General Public License v2
60 >
61 > import functools
62 > @@ -158,6 +158,7 @@ class _BinpkgFetcherProcess(SpawnProcess):
63 > self.env = fetch_env
64 > if settings.selinux_enabled():
65 > self._selinux_type =
66 > settings["PORTAGE_FETCH_T"]
67 > + self.log_filter_file =
68 > settings.get('PORTAGE_LOG_FILTER_FILE_CMD') SpawnProcess._start(self)
69 >
70 > def _pipe(self, fd_pipes):
71 > diff --git a/lib/_emerge/EbuildFetcher.py
72 > b/lib/_emerge/EbuildFetcher.py index 1e40994fb..55349c33c 100644
73 > --- a/lib/_emerge/EbuildFetcher.py
74 > +++ b/lib/_emerge/EbuildFetcher.py
75 > @@ -1,4 +1,4 @@
76 > -# Copyright 1999-2018 Gentoo Foundation
77 > +# Copyright 1999-2020 Gentoo Authors
78 > # Distributed under the terms of the GNU General Public License v2
79 >
80 > import copy
81 > @@ -225,6 +225,7 @@ class _EbuildFetcherProcess(ForkProcess):
82 > settings["NOCOLOR"] = nocolor
83 >
84 > self._settings = settings
85 > + self.log_filter_file =
86 > settings.get('PORTAGE_LOG_FILTER_FILE_CMD') ForkProcess._start(self)
87 >
88 > # Free settings now since it's no longer needed in
89 > diff --git a/lib/_emerge/EbuildPhase.py b/lib/_emerge/EbuildPhase.py
90 > index 477e0ba97..ddb3dc719 100644
91 > --- a/lib/_emerge/EbuildPhase.py
92 > +++ b/lib/_emerge/EbuildPhase.py
93 > @@ -26,6 +26,8 @@ from portage.package.ebuild.prepare_build_dirs
94 > import (_prepare_workdir, from portage.util.futures.compat_coroutine
95 > import coroutine from portage.util import writemsg
96 > from portage.util._async.AsyncTaskFuture import AsyncTaskFuture
97 > +from portage.util._async.BuildLogger import BuildLogger
98 > +from portage.util.futures import asyncio
99 > from portage.util.futures.executor.fork import ForkExecutor
100 >
101 > try:
102 > @@ -69,6 +71,11 @@ class EbuildPhase(CompositeTask):
103 > _locked_phases = ("setup", "preinst", "postinst", "prerm",
104 > "postrm")
105 > def _start(self):
106 > + future = asyncio.ensure_future(self._async_start(),
107 > loop=self.scheduler)
108 > + self._start_task(AsyncTaskFuture(future=future),
109 > self._async_start_exit) +
110 > + @coroutine
111 > + def _async_start(self):
112 >
113 > need_builddir = self.phase not in
114 > EbuildProcess._phases_without_builddir
115 > @@ -126,7 +133,7 @@ class EbuildPhase(CompositeTask):
116 > # Force background=True for this header
117 > since it's intended # for the log and it doesn't necessarily need to
118 > be visible # elsewhere.
119 > - self._elog('einfo', msg, background=True)
120 > + yield self._elog('einfo', msg,
121 > background=True)
122 > if self.phase == 'package':
123 > if 'PORTAGE_BINPKG_TMPFILE' not in
124 > self.settings: @@ -134,6 +141,12 @@ class EbuildPhase(CompositeTask):
125 > os.path.join(self.settings['PKGDIR'],
126 > self.settings['CATEGORY'],
127 > self.settings['PF']) + '.tbz2'
128 > + def _async_start_exit(self, task):
129 > + task.future.cancelled() or task.future.result()
130 > + if self._default_exit(task) != os.EX_OK:
131 > + self.wait()
132 > + return
133 > +
134 > if self.phase in ("pretend", "prerm"):
135 > env_extractor =
136 > BinpkgEnvExtractor(background=self.background,
137 > scheduler=self.scheduler, settings=self.settings) @@ -391,6 +404,7 @@
138 > class EbuildPhase(CompositeTask): self.returncode = 1
139 > self.wait()
140 >
141 > + @coroutine
142 > def _elog(self, elog_funcname, lines, background=None):
143 > if background is None:
144 > background = self.background
145 > @@ -407,11 +421,30 @@ class EbuildPhase(CompositeTask):
146 > portage.output.havecolor = global_havecolor
147 > msg = out.getvalue()
148 > if msg:
149 > - log_path = None
150 > - if self.settings.get("PORTAGE_BACKGROUND")
151 > != "subprocess":
152 > - log_path =
153 > self.settings.get("PORTAGE_LOG_FILE")
154 > - self.scheduler.output(msg, log_path=log_path,
155 > - background=background)
156 > + build_logger = None
157 > + try:
158 > + log_file = None
159 > + log_path = None
160 > + if
161 > self.settings.get("PORTAGE_BACKGROUND") != "subprocess":
162 > + log_path =
163 > self.settings.get("PORTAGE_LOG_FILE")
164 > + if log_path:
165 > + build_logger =
166 > BuildLogger(env=self.settings.environ(),
167 > + log_path=log_path,
168 > +
169 > log_filter_file=self.settings.get('PORTAGE_LOG_FILTER_FILE_CMD'),
170 > +
171 > scheduler=self.scheduler)
172 > + build_logger.start()
173 > + log_file = build_logger.stdin
174 > +
175 > + yield
176 > self.scheduler.async_output(msg, log_file=log_file,
177 > + background=background)
178 > +
179 > + if build_logger is not None:
180 > + build_logger.stdin.close()
181 > + yield
182 > build_logger.async_wait()
183 > + except asyncio.CancelledError:
184 > + if build_logger is not None:
185 > + build_logger.cancel()
186 > + raise
187 >
188 >
189 > class _PostPhaseCommands(CompositeTask):
190 > @@ -480,4 +513,4 @@ class _PostPhaseCommands(CompositeTask):
191 > qa_msg.extend("\t%s: %s" % (filename, "
192 > ".join(sorted(soname_deps))) for filename, soname_deps in unresolved)
193 > qa_msg.append("")
194 > - self.elog("eqawarn", qa_msg)
195 > + yield self.elog("eqawarn", qa_msg)
196 > diff --git a/lib/_emerge/SpawnProcess.py b/lib/_emerge/SpawnProcess.py
197 > index 395d66bb9..f96911571 100644
198 > --- a/lib/_emerge/SpawnProcess.py
199 > +++ b/lib/_emerge/SpawnProcess.py
200 > @@ -1,4 +1,4 @@
201 > -# Copyright 2008-2018 Gentoo Foundation
202 > +# Copyright 2008-2020 Gentoo Authors
203 > # Distributed under the terms of the GNU General Public License v2
204 >
205 > try:
206 > @@ -19,7 +19,10 @@ from portage.const import BASH_BINARY
207 > from portage.localization import _
208 > from portage.output import EOutput
209 > from portage.util import writemsg_level
210 > +from portage.util._async.BuildLogger import BuildLogger
211 > from portage.util._async.PipeLogger import PipeLogger
212 > +from portage.util.futures import asyncio
213 > +from portage.util.futures.compat_coroutine import coroutine
214 >
215 > class SpawnProcess(SubProcess):
216 >
217 > @@ -34,8 +37,8 @@ class SpawnProcess(SubProcess):
218 > "path_lookup", "pre_exec", "close_fds", "cgroup",
219 > "unshare_ipc", "unshare_mount", "unshare_pid",
220 > "unshare_net")
221 > - __slots__ = ("args",) + \
222 > - _spawn_kwarg_names + ("_pipe_logger",
223 > "_selinux_type",)
224 > + __slots__ = ("args", "log_filter_file") + \
225 > + _spawn_kwarg_names + ("_main_task", "_selinux_type",)
226 >
227 > # Max number of attempts to kill the processes listed in
228 > cgroup.procs, # given that processes may fork before they can be
229 > killed. @@ -137,13 +140,43 @@ class SpawnProcess(SubProcess):
230 > fcntl.fcntl(stdout_fd,
231 > fcntl.F_GETFD) |
232 > fcntl.FD_CLOEXEC)
233 > - self._pipe_logger =
234 > PipeLogger(background=self.background,
235 > + build_logger = BuildLogger(env=self.env,
236 > + log_path=log_file_path,
237 > + log_filter_file=self.log_filter_file,
238 > + scheduler=self.scheduler)
239 > + build_logger.start()
240 > +
241 > + pipe_logger = PipeLogger(background=self.background,
242 > scheduler=self.scheduler, input_fd=master_fd,
243 > - log_file_path=log_file_path,
244 > + log_file_path=build_logger.stdin,
245 > stdout_fd=stdout_fd)
246 > -
247 > self._pipe_logger.addExitListener(self._pipe_logger_exit)
248 > - self._pipe_logger.start()
249 > +
250 > + pipe_logger.start()
251 > +
252 > self._registered = True
253 > + self._main_task =
254 > asyncio.ensure_future(self._main(build_logger, pipe_logger),
255 > loop=self.scheduler)
256 > + self._main_task.add_done_callback(self._main_exit)
257 > +
258 > + @coroutine
259 > + def _main(self, build_logger, pipe_logger):
260 > + try:
261 > + if pipe_logger.poll() is None:
262 > + yield pipe_logger.async_wait()
263 > + if build_logger.poll() is None:
264 > + yield build_logger.async_wait()
265 > + except asyncio.CancelledError:
266 > + if pipe_logger.poll() is None:
267 > + pipe_logger.cancel()
268 > + if build_logger.poll() is None:
269 > + build_logger.cancel()
270 > + raise
271 > +
272 > + def _main_exit(self, main_task):
273 > + try:
274 > + main_task.result()
275 > + except asyncio.CancelledError:
276 > + self.cancel()
277 > + self._async_waitpid()
278 >
279 > def _can_log(self, slave_fd):
280 > return True
281 > @@ -167,20 +200,17 @@ class SpawnProcess(SubProcess):
282 >
283 > return spawn_func(args, **kwargs)
284 >
285 > - def _pipe_logger_exit(self, pipe_logger):
286 > - self._pipe_logger = None
287 > - self._async_waitpid()
288 > -
289 > def _unregister(self):
290 > SubProcess._unregister(self)
291 > if self.cgroup is not None:
292 > self._cgroup_cleanup()
293 > self.cgroup = None
294 > - if self._pipe_logger is not None:
295 > - self._pipe_logger.cancel()
296 > - self._pipe_logger = None
297 > + if self._main_task is not None:
298 > + self._main_task.done() or
299 > self._main_task.cancel()
300 > def _cancel(self):
301 > + if self._main_task is not None:
302 > + self._main_task.done() or
303 > self._main_task.cancel() SubProcess._cancel(self)
304 > self._cgroup_cleanup()
305 >
306 > diff --git a/lib/portage/dbapi/_MergeProcess.py
307 > b/lib/portage/dbapi/_MergeProcess.py index 371550079..236d1a255 100644
308 > --- a/lib/portage/dbapi/_MergeProcess.py
309 > +++ b/lib/portage/dbapi/_MergeProcess.py
310 > @@ -1,4 +1,4 @@
311 > -# Copyright 2010-2018 Gentoo Foundation
312 > +# Copyright 2010-2020 Gentoo Authors
313 > # Distributed under the terms of the GNU General Public License v2
314 >
315 > import io
316 > @@ -57,6 +57,7 @@ class MergeProcess(ForkProcess):
317 > self.fd_pipes = self.fd_pipes.copy()
318 > self.fd_pipes.setdefault(0,
319 > portage._get_stdin().fileno())
320 > + self.log_filter_file =
321 > self.settings.get('PORTAGE_LOG_FILTER_FILE_CMD') super(MergeProcess,
322 > self)._start()
323 > def _lock_vdb(self):
324 > diff --git a/lib/portage/package/ebuild/_config/special_env_vars.py
325 > b/lib/portage/package/ebuild/_config/special_env_vars.py index
326 > 440dd00b2..f44cb9b1b 100644 ---
327 > a/lib/portage/package/ebuild/_config/special_env_vars.py +++
328 > b/lib/portage/package/ebuild/_config/special_env_vars.py @@ -1,4 +1,4
329 > @@ -# Copyright 2010-2019 Gentoo Authors
330 > +# Copyright 2010-2020 Gentoo Authors
331 > # Distributed under the terms of the GNU General Public License v2
332 >
333 > from __future__ import unicode_literals
334 > @@ -175,7 +175,7 @@ environ_filter += [
335 > "PORTAGE_RO_DISTDIRS",
336 > "PORTAGE_RSYNC_EXTRA_OPTS", "PORTAGE_RSYNC_OPTS",
337 > "PORTAGE_RSYNC_RETRIES", "PORTAGE_SSH_OPTS",
338 > "PORTAGE_SYNC_STALE",
339 > - "PORTAGE_USE",
340 > + "PORTAGE_USE", "PORTAGE_LOG_FILTER_FILE_CMD",
341 > "PORTAGE_LOGDIR", "PORTAGE_LOGDIR_CLEAN",
342 > "QUICKPKG_DEFAULT_OPTS", "REPOMAN_DEFAULT_OPTS",
343 > "RESUMECOMMAND", "RESUMECOMMAND_FTP",
344 > @@ -204,7 +204,9 @@ default_globals = {
345 > 'PORTAGE_BZIP2_COMMAND': 'bzip2',
346 > }
347 >
348 > -validate_commands = ('PORTAGE_BZIP2_COMMAND',
349 > 'PORTAGE_BUNZIP2_COMMAND',) +validate_commands =
350 > ('PORTAGE_BZIP2_COMMAND', 'PORTAGE_BUNZIP2_COMMAND',
351 > + 'PORTAGE_LOG_FILTER_FILE_CMD',
352 > +)
353 >
354 > # To enhance usability, make some vars case insensitive
355 > # by forcing them to lower case.
356 > diff --git a/lib/portage/util/_async/BuildLogger.py
357 > b/lib/portage/util/_async/BuildLogger.py new file mode 100644
358 > index 000000000..f5fea77ea
359 > --- /dev/null
360 > +++ b/lib/portage/util/_async/BuildLogger.py
361 > @@ -0,0 +1,109 @@
362 > +# Copyright 2020 Gentoo Authors
363 > +# Distributed under the terms of the GNU General Public License v2
364 > +
365 > +import subprocess
366 > +
367 > +from portage import os
368 > +from portage.util import shlex_split
369 > +from _emerge.AsynchronousTask import AsynchronousTask
370 > +from portage.util._async.PipeLogger import PipeLogger
371 > +from portage.util._async.PopenProcess import PopenProcess
372 > +from portage.util.futures import asyncio
373 > +from portage.util.futures.compat_coroutine import coroutine
374 > +
375 > +
376 > +class BuildLogger(AsynchronousTask):
377 > + """
378 > + Write to a log file, with compression support provided by
379 > PipeLogger.
380 > + If the log_filter_file parameter is specified, then it is
381 > interpreted
382 > + as a command to execute which filters log output (see the
383 > + PORTAGE_LOG_FILTER_FILE_CMD variable in make.conf(5)). The
384 > stdin property
385 > + provides access to a writable binary file stream (refers to
386 > a pipe)
387 > + that log content should be written to (usually redirected
388 > from
389 > + subprocess stdout and stderr streams).
390 > + """
391 > +
392 > + __slots__ = ('env', 'log_path', 'log_filter_file',
393 > '_main_task', '_stdin') +
394 > + @property
395 > + def stdin(self):
396 > + return self._stdin
397 > +
398 > + def _start(self):
399 > + filter_proc = None
400 > + log_input = None
401 > + if self.log_path is not None:
402 > + log_filter_file = self.log_filter_file
403 > + if log_filter_file is not None:
404 > + split_value =
405 > shlex_split(log_filter_file)
406 > + log_filter_file = split_value if
407 > split_value else None
408 > + if log_filter_file:
409 > + filter_input, stdin = os.pipe()
410 > + log_input, filter_output = os.pipe()
411 > + try:
412 > + filter_proc = PopenProcess(
413 > +
414 > proc=subprocess.Popen(
415 > +
416 > log_filter_file,
417 > + env=self.env,
418 > +
419 > stdin=filter_input,
420 > +
421 > stdout=filter_output,
422 > +
423 > stderr=filter_output,
424 > + ),
425 > +
426 > scheduler=self.scheduler,
427 > + )
428 > + filter_proc.start()
429 > + except EnvironmentError:
430 > + # Maybe the command is
431 > missing or broken somehow...
432 > + os.close(filter_input)
433 > + os.close(stdin)
434 > + os.close(log_input)
435 > + os.close(filter_output)
436 > + else:
437 > + self._stdin =
438 > os.fdopen(stdin, 'wb', 0)
439 > + os.close(filter_input)
440 > + os.close(filter_output)
441 > +
442 > + if self._stdin is None:
443 > + # Since log_filter_file is unspecified or
444 > refers to a file
445 > + # that is missing or broken somehow, create
446 > a pipe that
447 > + # logs directly to pipe_logger.
448 > + log_input, stdin = os.pipe()
449 > + self._stdin = os.fdopen(stdin, 'wb', 0)
450 > +
451 > + # Set background=True so that pipe_logger does not
452 > log to stdout.
453 > + pipe_logger = PipeLogger(background=True,
454 > + scheduler=self.scheduler, input_fd=log_input,
455 > + log_file_path=self.log_path)
456 > + pipe_logger.start()
457 > +
458 > + self._main_task =
459 > asyncio.ensure_future(self._main(filter_proc, pipe_logger),
460 > loop=self.scheduler)
461 > + self._main_task.add_done_callback(self._main_exit)
462 > +
463 > + @coroutine
464 > + def _main(self, filter_proc, pipe_logger):
465 > + try:
466 > + if pipe_logger.poll() is None:
467 > + yield pipe_logger.async_wait()
468 > + if filter_proc is not None and
469 > filter_proc.poll() is None:
470 > + yield filter_proc.async_wait()
471 > + except asyncio.CancelledError:
472 > + if pipe_logger.poll() is None:
473 > + pipe_logger.cancel()
474 > + if filter_proc is not None and
475 > filter_proc.poll() is None:
476 > + filter_proc.cancel()
477 > + raise
478 > +
479 > + def _cancel(self):
480 > + if self._main_task is not None:
481 > + self._main_task.done() or
482 > self._main_task.cancel()
483 > + if self._stdin is not None and not
484 > self._stdin.closed:
485 > + self._stdin.close()
486 > +
487 > + def _main_exit(self, main_task):
488 > + try:
489 > + main_task.result()
490 > + except asyncio.CancelledError:
491 > + self.cancel()
492 > + self._was_cancelled()
493 > + self.returncode = self.returncode or 0
494 > + self._async_wait()
495 > diff --git a/lib/portage/util/_async/SchedulerInterface.py
496 > b/lib/portage/util/_async/SchedulerInterface.py index
497 > ec6417da1..3ff250d1d 100644 ---
498 > a/lib/portage/util/_async/SchedulerInterface.py +++
499 > b/lib/portage/util/_async/SchedulerInterface.py @@ -1,4 +1,4 @@
500 > -# Copyright 2012-2018 Gentoo Foundation
501 > +# Copyright 2012-2020 Gentoo Authors
502 > # Distributed under the terms of the GNU General Public License v2
503 >
504 > import gzip
505 > @@ -7,6 +7,8 @@ import errno
506 > from portage import _encodings
507 > from portage import _unicode_encode
508 > from portage.util import writemsg_level
509 > +from portage.util.futures._asyncio.streams import _writer
510 > +from portage.util.futures.compat_coroutine import coroutine
511 > from ..SlotObject import SlotObject
512 >
513 > class SchedulerInterface(SlotObject):
514 > @@ -53,6 +55,34 @@ class SchedulerInterface(SlotObject):
515 > def _return_false():
516 > return False
517 >
518 > + @coroutine
519 > + def async_output(self, msg, log_file=None, background=None,
520 > + level=0, noiselevel=-1):
521 > + """
522 > + Output a msg to stdio (if not in background) and to
523 > a log file
524 > + if provided.
525 > +
526 > + @param msg: a message string, including newline if
527 > appropriate
528 > + @type msg: str
529 > + @param log_file: log file in binary mode
530 > + @type log_file: file
531 > + @param background: send messages only to log (not to
532 > stdio)
533 > + @type background: bool
534 > + @param level: a numeric logging level (see the
535 > logging module)
536 > + @type level: int
537 > + @param noiselevel: passed directly to writemsg
538 > + @type noiselevel: int
539 > + """
540 > + global_background = self._is_background()
541 > + if background is None or global_background:
542 > + background = global_background
543 > +
544 > + if not background:
545 > + writemsg_level(msg, level=level,
546 > noiselevel=noiselevel) +
547 > + if log_file is not None:
548 > + yield _writer(log_file, _unicode_encode(msg))
549 > +
550 > def output(self, msg, log_path=None, background=None,
551 > level=0, noiselevel=-1):
552 > """
553 > diff --git a/man/make.conf.5 b/man/make.conf.5
554 > index a3bd662ae..eb812150f 100644
555 > --- a/man/make.conf.5
556 > +++ b/man/make.conf.5
557 > @@ -1,4 +1,4 @@
558 > -.TH "MAKE.CONF" "5" "May 2020" "Portage VERSION" "Portage"
559 > +.TH "MAKE.CONF" "5" "Jun 2020" "Portage VERSION" "Portage"
560 > .SH "NAME"
561 > make.conf \- custom settings for Portage
562 > .SH "SYNOPSIS"
563 > @@ -979,6 +979,11 @@ with an integer pid. For example, a value of
564 > "ionice \-c 3 \-p \\${PID}" will set idle io priority. For more
565 > information about ionice, see \fBionice\fR(1). This variable is unset
566 > by default. .TP
567 > +.B PORTAGE_LOG_FILTER_FILE_CMD
568 > +This variable specifies a command that filters build log output to a
569 > +log file. In order to filter ANSI escape codes from build logs,
570 > +\fBansifilter\fR(1) is a convenient setting for this variable.
571 > +.TP
572 > .B PORTAGE_LOGDIR
573 > This variable defines the directory in which per\-ebuild logs are
574 > kept. Logs are created only when this is set. They are stored as
575
576 That's a lot of code...but I couldn't spot anything wrong, so looks good

Replies