Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 5180 tempfiles are leak in tmp #5210

Merged
merged 12 commits into from
Aug 3, 2022
2 changes: 2 additions & 0 deletions news/5210.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Write output from ``subprocess_run`` directly to ``stdout`` instead of creating temporary file.
Remove deprecated ``distutils.sysconfig``, use ``sysconfig``.
99 changes: 18 additions & 81 deletions pipenv/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import site
import sys
from pathlib import Path
from sysconfig import get_paths, get_python_version, get_scheme_names
from sysconfig import get_paths, get_scheme_names

import pkg_resources

Expand Down Expand Up @@ -159,17 +159,6 @@ def python_info(self) -> Dict[str, str]:
return {"py_version_short": py_version_short, "abiflags": abiflags}
return {}

def _replace_parent_version(self, path: str, replace_version: str) -> str:
if not os.path.exists(path):
base, leaf = os.path.split(path)
base, parent = os.path.split(base)
leaf = os.path.join(parent, leaf).replace(
replace_version,
self.python_info.get("py_version_short", get_python_version()),
)
return os.path.join(base, leaf)
return path

@cached_property
def install_scheme(self):
if "venv" in get_scheme_names():
Expand Down Expand Up @@ -214,30 +203,7 @@ def base_paths(self) -> Dict[str, str]:
if self._base_paths:
paths = self._base_paths.copy()
else:
try:
paths = self.get_paths()
except Exception:
paths = get_paths(
self.install_scheme,
vars={
"base": prefix,
"platbase": prefix,
},
)
current_version = get_python_version()
try:
for k in list(paths.keys()):
if not os.path.exists(paths[k]):
paths[k] = self._replace_parent_version(
paths[k], current_version
)
except OSError:
# Sometimes virtualenvs are made using virtualenv interpreters and there is no
# include directory, which will cause this approach to fail. This failsafe
# will make sure we fall back to the shell execution to find the real include path
paths = self.get_include_path()
paths.update(self.get_lib_paths())
paths["scripts"] = self.script_basedir
paths = self.get_paths()
if not paths:
paths = get_paths(
self.install_scheme,
Expand Down Expand Up @@ -342,36 +308,28 @@ def build_command(
pylib_lines = []
pyinc_lines = []
py_command = (
"import sysconfig, distutils.sysconfig, io, json, sys; paths = {{"
"%s }}; value = u'{{0}}'.format(json.dumps(paths));"
"fh = io.open('{0}', 'w'); fh.write(value); fh.close()"
"import sysconfig, json; paths = {%s};"
"value = u'{0}'.format(json.dumps(paths)); print(value)"
)
distutils_line = "distutils.sysconfig.get_python_{0}(plat_specific={1})"
sysconfig_line = "sysconfig.get_path('{0}')"
if python_lib:
for key, var, val in (("pure", "lib", "0"), ("plat", "lib", "1")):
dist_prefix = f"{key}lib"
# XXX: We need to get 'stdlib' or 'platstdlib'
sys_prefix = "{}stdlib".format("" if key == "pure" else key)
for key in ("purelib", "platlib", "stdlib", "platstdlib"):
pylib_lines.append(
f"u'{dist_prefix}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})"
)
pylib_lines.append(
f"u'{sys_prefix}': u'{{{{0}}}}'.format({sysconfig_line.format(sys_prefix)})"
f"u'{key}': u'{{0}}'.format({sysconfig_line.format(key)})"
)
if python_inc:
for key, var, val in (("include", "inc", "0"), ("platinclude", "inc", "1")):
pylib_lines.append(
f"u'{key}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})"
for key in ("include", "platinclude"):
pyinc_lines.append(
f"u'{key}': u'{{0}}'.format({sysconfig_line.format(key)})"
)
lines = pylib_lines + pyinc_lines
if scripts:
lines.append(
"u'scripts': u'{{0}}'.format(%s)" % sysconfig_line.format("scripts")
"u'scripts': u'{0}'.format(%s)" % sysconfig_line.format("scripts")
)
if py_version:
lines.append(
"u'py_version_short': u'{{0}}'.format(distutils.sysconfig.get_python_version()),"
"u'py_version_short': u'{0}'.format(sysconfig.get_python_version()),"
)
lines_as_str = ",".join(lines)
py_command = py_command % lines_as_str
Expand All @@ -384,18 +342,13 @@ def get_paths(self) -> Optional[Dict[str, str]]:
:return: The python paths for the environment
:rtype: Dict[str, str]
"""
tmpfile = vistir.path.create_tracked_tempfile(suffix=".json")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus points for removing usage of vistir 💯

tmpfile.close()
tmpfile_path = make_posix(tmpfile.name)
py_command = self.build_command(
python_lib=True, python_inc=True, scripts=True, py_version=True
)
command = [self.python, "-c", py_command.format(tmpfile_path)]
command = [self.python, "-c", py_command]
c = subprocess_run(command)
if c.returncode == 0:
paths = {}
with open(tmpfile_path, "r", encoding="utf-8") as fh:
paths = json.load(fh)
paths = json.loads(c.stdout)
if "purelib" in paths:
paths["libdir"] = paths["purelib"] = make_posix(paths["purelib"])
for key in (
Expand All @@ -420,17 +373,12 @@ def get_lib_paths(self) -> Dict[str, str]:
:return: The python include path for the environment
:rtype: Dict[str, str]
"""
tmpfile = vistir.path.create_tracked_tempfile(suffix=".json")
tmpfile.close()
tmpfile_path = make_posix(tmpfile.name)
py_command = self.build_command(python_lib=True)
command = [self.python, "-c", py_command.format(tmpfile_path)]
command = [self.python, "-c", py_command]
c = subprocess_run(command)
paths = None
if c.returncode == 0:
paths = {}
with open(tmpfile_path, "r", encoding="utf-8") as fh:
paths = json.load(fh)
paths = json.loads(c.stdout)
if "purelib" in paths:
paths["libdir"] = paths["purelib"] = make_posix(paths["purelib"])
for key in ("platlib", "platstdlib", "stdlib"):
Expand Down Expand Up @@ -476,22 +424,11 @@ def get_include_path(self) -> Optional[Dict[str, str]]:
:return: The python include path for the environment
:rtype: Dict[str, str]
"""
tmpfile = vistir.path.create_tracked_tempfile(suffix=".json")
tmpfile.close()
tmpfile_path = make_posix(tmpfile.name)
py_command = (
"import distutils.sysconfig, io, json, sys; paths = {{u'include': "
"u'{{0}}'.format(distutils.sysconfig.get_python_inc(plat_specific=0)), "
"u'platinclude': u'{{0}}'.format(distutils.sysconfig.get_python_inc("
"plat_specific=1)) }}; value = u'{{0}}'.format(json.dumps(paths));"
"fh = io.open('{0}', 'w'); fh.write(value); fh.close()"
)
command = [self.python, "-c", py_command.format(tmpfile_path)]
py_command = self.build_command(python_inc=True)
command = [self.python, "-c", py_command]
c = subprocess_run(command)
if c.returncode == 0:
paths = []
with open(tmpfile_path, "r", encoding="utf-8") as fh:
paths = json.load(fh)
paths = json.loads(c.stdout)
for key in ("include", "platinclude"):
if key in paths:
paths[key] = make_posix(paths[key])
Expand Down