Skip to content

Commit

Permalink
Fixed the incorrect operation when setuptools plugins output someth…
Browse files Browse the repository at this point in the history
…ing into `stdout`. (#2335)
  • Loading branch information
KOLANICH authored Jun 29, 2022
1 parent 245cae7 commit 8856f51
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/changelog/2335.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the incorrect operation when ``setuptools`` plugins output something into ``stdout``.
41 changes: 40 additions & 1 deletion src/virtualenv/discovery/cached_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
19 changes: 18 additions & 1 deletion src/virtualenv/discovery/py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,4 +524,21 @@ def _possible_base(self):
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])))

0 comments on commit 8856f51

Please sign in to comment.