fix: ask nothing when the output goes to a file

install.py > log.txt at a terminal wrote the menu into the file and
waited for an answer nobody saw. A question now needs stdin and stdout on
a terminal, otherwise the run goes on as with --no-input and says so.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 22:30:47 +02:00
1 parent c53715d7b9
commit e003828e26
3 files changed
+40 -1

No files matched your search

+15
View File
@@ -8933,6 +8933,14 @@ def _unknown_target(target: str, plat: str, targets: dict) -> None:
print(f" {plat} publishes no target list", file=sys.stderr)
def _is_terminal(stream: TextIO) -> bool:
isatty = getattr(stream, "isatty", None)
try:
return bool(isatty and isatty())
except (OSError, ValueError):
return False
def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
"""One run of the installer, once the options are read."""
KEPT.reset()
@@ -8940,6 +8948,13 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
interactive = sys.stdin.isatty() and not args.no_input and not args.quiet
listing = (args.list_targets or args.list_systems or args.list_cores
or args.list_emulators)
if interactive and not _is_terminal(real_stdout):
# A question goes to stdout: sent to a file, it waited on an answer
# to a prompt nobody could see.
interactive = False
if not listing and not args.check:
print("Output goes to a file, so nothing is asked: the run goes on as "
"with --no-input.", file=sys.stderr)
print(f"{term.bold('RetroBIOS')} {term.dim(installer_id())}\n")
os_type = detect_os()
+24
View File
@@ -566,6 +566,30 @@ class AFolderThisUserCannotEnter(unittest.TestCase):
self.assertEqual(state, "wrong_hash")
class OutputToAFileAsksNothing(unittest.TestCase):
"""`install.py > log.txt` at a terminal: the menu went into the file
and the run waited on an answer to a prompt nobody could see."""
@unittest.skipIf(os.name == "nt", "a pseudo-terminal for stdin")
def test_the_run_goes_on_as_with_no_input(self):
import pty
master, slave = pty.openpty()
try:
with FakeRepository() as repo:
proc = subprocess.run(
repo.command(), env=repo.env(), stdin=slave, capture_output=True,
text=True, timeout=60, check=False)
installed = repo.installed()
finally:
os.close(master)
os.close(slave)
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertIn("nothing is asked", proc.stderr)
self.assertNotIn("Enter) install everything", proc.stdout)
self.assertEqual(len(installed), len(FakeRepository.PAYLOADS))
class _Tty(io.StringIO):
def isatty(self) -> bool:
return True
+1 -1
View File
@@ -701,7 +701,7 @@ what the options name, everything by default.
| <span id="option-list-cores"></span>`--list-cores` | Print the cores whose extra files a platform installs |
| <span id="option-list-emulators"></span>`--list-emulators` | Print every name `--emulator` accepts, each buildbot name beside the emulator it stands for |
| <span id="option-romm-firmware"></span>`--romm-firmware TEMPLATE` | RomM: where each platform's firmware goes in the library, as `filesystem.structure.firmware` in RomM's `config.yml` says (`{platform}/bios` for a library laid out as `{platform}/roms`). Read from `config.yml` beside the library when it is there. See [RomM firmware layout](#romm-firmware-layout) |
| <span id="option-no-input"></span>`--no-input` | Never ask a question, even at a terminal |
| <span id="option-no-input"></span>`--no-input` | Never ask a question, even at a terminal. A run whose output goes to a file or a pipe asks nothing either, and says so on stderr |
| <span id="option-check"></span>`--check` | Report and exit without writing |
| <span id="option-dry-run"></span>`--dry-run` | Show what would be downloaded, the totals first, then by system and with sizes, without writing anything. A plan taller than the screen names the ten largest systems, and `--verbose` lists every system and file |
| <span id="option-yes"></span>`--yes`, `-y` | Do not ask before a download of 1 GB or more |