diff --git a/pyproject-rpm-macros.spec b/pyproject-rpm-macros.spec index 667cda0..f1c991c 100644 --- a/pyproject-rpm-macros.spec +++ b/pyproject-rpm-macros.spec @@ -12,7 +12,7 @@ License: MIT # Increment Y and reset Z when new macros or features are added # Increment Z when this is a bugfix or a cosmetic change # Dropping support for EOL Fedoras is *not* considered a breaking change -Version: 1.6.0 +Version: 1.6.1 Release: 1%{?dist} # Macro files @@ -148,6 +148,9 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %changelog +* Fri Feb 03 2023 Miro Hrončok - 1.6.1-1 +- %%pyproject_buildrequires: Avoid leaking stdout from subprocesses + * Fri Jan 20 2023 Miro Hrončok - 1.6.0-1 - Add pyproject-srpm-macros with a minimal %%pyproject_buildrequires macro diff --git a/pyproject_buildrequires.py b/pyproject_buildrequires.py index 01c57f1..323ab2a 100644 --- a/pyproject_buildrequires.py +++ b/pyproject_buildrequires.py @@ -4,9 +4,9 @@ import os import sys import importlib.metadata import argparse +import tempfile import traceback import contextlib -from io import StringIO import json import subprocess import re @@ -48,11 +48,35 @@ from pyproject_convert import convert @contextlib.contextmanager def hook_call(): - captured_out = StringIO() - with contextlib.redirect_stdout(captured_out): + """Context manager that records all stdout content (on FD level) + and prints it to stderr at the end, with a 'HOOK STDOUT: ' prefix.""" + tmpfile = io.TextIOWrapper( + tempfile.TemporaryFile(buffering=0), + encoding='utf-8', + errors='replace', + write_through=True, + ) + + stdout_fd = 1 + stdout_fd_dup = os.dup(stdout_fd) + stdout_orig = sys.stdout + + # begin capture + sys.stdout = tmpfile + os.dup2(tmpfile.fileno(), stdout_fd) + + try: yield - for line in captured_out.getvalue().splitlines(): - print_err('HOOK STDOUT:', line) + finally: + # end capture + sys.stdout = stdout_orig + os.dup2(stdout_fd_dup, stdout_fd) + + tmpfile.seek(0) # rewind + for line in tmpfile: + print_err('HOOK STDOUT:', line, end='') + + tmpfile.close() def guess_reason_for_invalid_requirement(requirement_str): diff --git a/pyproject_buildrequires_testcases.yaml b/pyproject_buildrequires_testcases.yaml index 75934df..1a2e6bb 100644 --- a/pyproject_buildrequires_testcases.yaml +++ b/pyproject_buildrequires_testcases.yaml @@ -818,3 +818,21 @@ Pre-releases are accepted: python3dist(wheel) stderr_contains: "Requirement satisfied: cffi" result: 0 + + +Wrapped subprocess prints to stdout from setup.py: + installed: + setuptools: 50 + wheel: 1 + include_runtime: false + setup.py: | + import os + os.system('echo LEAK?') + from setuptools import setup + setup(name='test', version='0.1') + expected: | + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + stderr_contains: "HOOK STDOUT: LEAK?" + result: 0