Fixed the incorrect operation when setuptools plugins output something into stdout. (#2335)
This commit is contained in:
parent
245cae75ec
commit
8856f51766
3 changed files with 59 additions and 2 deletions
1
docs/changelog/2335.bugfix.rst
Normal file
1
docs/changelog/2335.bugfix.rst
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix the incorrect operation when ``setuptools`` plugins output something into ``stdout``.
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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])))
|
||||
|
|
|
|||
Loading…
Reference in a new issue