%pyproject_buildrequires: Avoid leaking stdout from subprocesses
When the build backend prints to stdout via non-Python means, for example when a setup.py script calls a verbose program via os.system(), the output leaked to stdout of %pyproject_buildrequires was treated as generated BuildRequires. Fore example, if the setup.py script has: rv = os.system('/usr/bin/patch -N -p3 -d build/lib < lib/py-lmdb/env-copy-txn.patch') (From https://github.com/jnwatson/py-lmdb/blob/py-lmdb_1.0.0/setup.py#L117) The stdout of /usr/bin/patch leaked to stdout of %pyproject_buildrequires: [lmdb-1.0.0]$ /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --python3_pkgversion 3 2>/dev/null python3dist(setuptools) >= 40.8 python3dist(wheel) patching file lmdb.h patching file mdb.c python3dist(wheel) patching file lmdb.h patching file mdb.c This resulted in DNF errors like this: No matching package to install: 'lmdb.h' No matching package to install: 'mdb.c' No matching package to install: 'patching' Moreover, it resulted in bogus BuildRequires that may have existed (e.g. "file"). By replacing the usage of contextlib.redirect_stdout (which only redirects Python's sys.stdout) with a custom context manager that captures stdout on file descriptor level (in addition to Python's sys.stdout), we avoid this leak. File descriptor magic heavily inspired by the capfd pytest fixture. Related: rhbz#2168193
This commit is contained in:
parent
159b22e742
commit
ecf0a140a8
@ -12,7 +12,7 @@ License: MIT
|
|||||||
# Increment Y and reset Z when new macros or features are added
|
# Increment Y and reset Z when new macros or features are added
|
||||||
# Increment Z when this is a bugfix or a cosmetic change
|
# Increment Z when this is a bugfix or a cosmetic change
|
||||||
# Dropping support for EOL Fedoras is *not* considered a breaking change
|
# Dropping support for EOL Fedoras is *not* considered a breaking change
|
||||||
Version: 1.6.0
|
Version: 1.6.1
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
|
|
||||||
# Macro files
|
# Macro files
|
||||||
@ -148,6 +148,9 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
|
|||||||
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Fri Feb 03 2023 Miro Hrončok <mhroncok@redhat.com> - 1.6.1-1
|
||||||
|
- %%pyproject_buildrequires: Avoid leaking stdout from subprocesses
|
||||||
|
|
||||||
* Fri Jan 20 2023 Miro Hrončok <miro@hroncok.cz> - 1.6.0-1
|
* Fri Jan 20 2023 Miro Hrončok <miro@hroncok.cz> - 1.6.0-1
|
||||||
- Add pyproject-srpm-macros with a minimal %%pyproject_buildrequires macro
|
- Add pyproject-srpm-macros with a minimal %%pyproject_buildrequires macro
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import argparse
|
import argparse
|
||||||
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
import contextlib
|
import contextlib
|
||||||
from io import StringIO
|
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
@ -48,11 +48,35 @@ from pyproject_convert import convert
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def hook_call():
|
def hook_call():
|
||||||
captured_out = StringIO()
|
"""Context manager that records all stdout content (on FD level)
|
||||||
with contextlib.redirect_stdout(captured_out):
|
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
|
yield
|
||||||
for line in captured_out.getvalue().splitlines():
|
finally:
|
||||||
print_err('HOOK STDOUT:', line)
|
# 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):
|
def guess_reason_for_invalid_requirement(requirement_str):
|
||||||
|
@ -818,3 +818,21 @@ Pre-releases are accepted:
|
|||||||
python3dist(wheel)
|
python3dist(wheel)
|
||||||
stderr_contains: "Requirement satisfied: cffi"
|
stderr_contains: "Requirement satisfied: cffi"
|
||||||
result: 0
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user