Fixed the incorrect operation when setuptools plugins output something into stdout. (#2335)

This commit is contained in:
KOLANICH 2022-06-29 00:59:17 +00:00 committed by GitHub
parent 245cae75ec
commit 8856f51766
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 2 deletions

View file

@ -0,0 +1 @@
Fix the incorrect operation when ``setuptools`` plugins output something into ``stdout``.

View file

@ -8,8 +8,10 @@ from __future__ import absolute_import, unicode_literals
import logging
import os
import random
import sys
from collections import OrderedDict
from string import ascii_lowercase, ascii_uppercase, digits
from virtualenv.app_data import AppDataDisabled
from virtualenv.discovery.py_info import PythonInfo
@ -85,10 +87,27 @@ def _get_via_file_cache(cls, app_data, path, exe, env):
return py_info
COOKIE_LENGTH = 32 # type: int
def gen_cookie():
return "".join(random.choice("".join((ascii_lowercase, ascii_uppercase, digits))) for _ in range(COOKIE_LENGTH))
def _run_subprocess(cls, exe, app_data, env):
py_info_script = Path(os.path.abspath(__file__)).parent / "py_info.py"
# Cookies allow to split the serialized stdout output generated by the script collecting the info from the output
# generated by something else. The right way to deal with it is to create an anonymous pipe and pass its descriptor
# to the child and output to it. But AFAIK all of them are either not cross-platform or too big to implement and are
# not in the stdlib. So the easiest and the shortest way I could mind is just using the cookies.
# We generate pseudorandom cookies because it easy to implement and avoids breakage from outputting modules source
# code, i.e. by debug output libraries. We reverse the cookies to avoid breakages resulting from variable values
# appearing in debug output.
start_cookie = gen_cookie()
end_cookie = gen_cookie()
with app_data.ensure_extracted(py_info_script) as py_info_script:
cmd = [exe, str(py_info_script)]
cmd = [exe, str(py_info_script), start_cookie, end_cookie]
# prevent sys.prefix from leaking into the child process - see https://bugs.python.org/issue22490
env = env.copy()
env.pop("__PYVENV_LAUNCHER__", None)
@ -108,6 +127,26 @@ def _run_subprocess(cls, exe, app_data, env):
out, err, code = "", os_error.strerror, os_error.errno
result, failure = None, None
if code == 0:
out_starts = out.find(start_cookie[::-1])
if out_starts > -1:
pre_cookie = out[:out_starts]
if pre_cookie:
sys.stdout.write(pre_cookie)
out = out[out_starts + COOKIE_LENGTH :]
out_ends = out.find(end_cookie[::-1])
if out_ends > -1:
post_cookie = out[out_ends + COOKIE_LENGTH :]
if post_cookie:
sys.stdout.write(post_cookie)
out = out[:out_ends]
result = cls._from_json(out)
result.executable = exe # keep original executable as this may contain initialization code
else:

View file

@ -524,4 +524,21 @@ class PythonInfo(object):
if __name__ == "__main__":
# dump a JSON representation of the current python
# noinspection PyProtectedMember
print(PythonInfo()._to_json())
argv = sys.argv[1:]
if len(argv) >= 1:
start_cookie = argv[0]
argv = argv[1:]
else:
start_cookie = ""
if len(argv) >= 1:
end_cookie = argv[0]
argv = argv[1:]
else:
end_cookie = ""
sys.argv = sys.argv[:1] + argv
info = PythonInfo()._to_json()
sys.stdout.write("".join((start_cookie[::-1], info, end_cookie[::-1])))