pmb: Make RunOutputTypeDefault and RunOutputTypePopen enums

This allows us to get rid of some of the validation in sanity_checks()
as mypy handles this validation at "build time", and any typos in the
enum instantiation would be a runtime error rather than a silent
failure.

Additionally, it allows us to encode some of the behaviour of the
different output types into the type definition itself by using methods.

Part-of: https://gitlab.postmarketos.org/postmarketOS/pmbootstrap/-/merge_requests/2642
(cherry picked from commit 7d2f055bcb)

pmb: Fix PmbArgs containing a string for output

While the annotations were changed to suggest that the output property
of PmbArgs contains a RunOutputType, at runtime it actually contained a
string because the argument parsing code hadn't been adapted to create a
RunOutputType. Fix this, and also change it to RunOutputTypeDefault as
while at it as that's more accurate.

Fixes 7d2f055bcb
Part-of: https://gitlab.postmarketos.org/postmarketOS/pmbootstrap/-/merge_requests/2644
This commit is contained in:
Newbyte 2025-07-07 17:00:58 +02:00
parent 3ea5a3433b
commit 861de1e507
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
21 changed files with 193 additions and 88 deletions

View file

@ -2,7 +2,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import fcntl
from pmb.core.context import get_context
from pmb.types import PathString, Env, RunOutputType, RunReturnType
from pmb.types import (
PathString,
Env,
RunOutputType,
RunOutputTypeDefault,
RunOutputTypePopen,
RunReturnType,
)
from pmb.helpers import logging
import os
from pathlib import Path
@ -51,25 +58,23 @@ def flat_cmd(
def sanity_checks(
output: RunOutputType = "log", output_return: bool = False, check: bool | None = None
output: RunOutputType = RunOutputTypeDefault.LOG,
output_return: bool = False,
check: bool | None = None,
) -> None:
"""Raise an exception if the parameters passed to core() don't make sense.
(all parameters are described in core() below).
"""
vals = ["log", "stdout", "interactive", "tui", "background", "pipe", "null"]
if output not in vals:
raise RuntimeError("Invalid output value: " + str(output))
# Prevent setting the check parameter with output="background".
# The exit code won't be checked when running in background, so it would
# always by check=False. But we prevent it from getting set to check=False
# as well, so it does not look like you could change it to check=True.
if check is not None and output == "background":
if check is not None and output == RunOutputTypePopen.BACKGROUND:
raise RuntimeError("Can't use check with output: background")
if output_return and output in ["tui", "background"]:
raise RuntimeError("Can't use output_return with output: " + output)
if output_return and output in [RunOutputTypeDefault.TUI, RunOutputTypePopen.BACKGROUND]:
raise RuntimeError(f"Can't use output_return with output: {output}")
def background(
@ -361,7 +366,7 @@ def core(
log_message: str,
cmd: Sequence[PathString],
working_dir: Path | None = None,
output: RunOutputType = "log",
output: RunOutputType = RunOutputTypeDefault.LOG,
output_return: bool = False,
check: bool | None = None,
sudo: bool = False,
@ -380,18 +385,18 @@ def core(
:param working_dir: path in host system where the command should run
:param output: where to write the output (stdout and stderr) of the
process. We almost always write to the log file, which can
be read with "pmbootstrap log" (output values: "log",
"stdout", "interactive", "background"), so it's easy to
be read with "pmbootstrap log" (output values: LOG,
STDOUT, INTERACTIVE, BACKGROUND), so it's easy to
trace what pmbootstrap does.
The exceptions are "tui" (text-based user interface), where
The exceptions are TUI (text-based user interface), where
it does not make sense to write to the log file (think of
ncurses UIs, such as "menuconfig") and "pipe" where the
output is written to a pipe for manual asynchronous
consumption by the caller.
When the output is not set to "interactive", "tui",
"background" or "pipe", we kill the process if it does not
When the output is not set to INTERACTIVE, TUI,
BACKGROUND or PIPE, we kill the process if it does not
output anything for 5 minutes (time can be set with
"pmbootstrap --timeout").
@ -399,17 +404,17 @@ def core(
their properties. "wait" indicates that we wait for the
process to complete.
============= ======= ========== ============= ==== ==========
output value timeout out to log out to stdout wait pass stdin
============= ======= ========== ============= ==== ==========
"log" x x x
"stdout" x x x x
"interactive" x x x x
"tui" x x x
"background" x
"pipe"
"null"
============= ======= ========== ============= ==== ==========
============ ======= ========== ============= ==== ==========
output value timeout out to log out to stdout wait pass stdin
============ ======= ========== ============= ==== ==========
LOG x x x
STDOUT x x x x
INTERACTIVE x x x x
TUI x x x
BACKGROUND x
PIPE
NULL
============ ======= ========== ============= ==== ==========
:param output_return: in addition to writing the program's output to the
destinations above in real time, write to a buffer and return it as string when the
@ -438,34 +443,34 @@ def core(
# raise e
# Background
if output == "background":
if output == RunOutputTypePopen.BACKGROUND:
return background(cmd, working_dir)
# Pipe
if output == "pipe":
if output == RunOutputTypePopen.PIPE:
return pipe(cmd, working_dir)
# Foreground
output_after_run = ""
if output == "tui":
if output == RunOutputTypeDefault.TUI:
# Foreground TUI
code = foreground_tui(cmd, working_dir)
else:
# Foreground pipe (always redirects to the error log file)
output_to_stdout = False
if not context.details_to_stdout and output in ["stdout", "interactive"]:
if not context.details_to_stdout and output.is_to_stdout():
output_to_stdout = True
output_timeout = output in ["log", "stdout"] and not disable_timeout
output_timeout = output.has_timeout() and not disable_timeout
stdin = subprocess.DEVNULL if output in ["log", "stdout"] else None
stdin = None if output.has_pass_stdin() else subprocess.DEVNULL
(code, output_after_run) = foreground_pipe(
cmd,
working_dir,
output_to_stdout,
output_return,
output != "null",
output != RunOutputTypeDefault.NULL,
output_timeout,
sudo,
stdin,