Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/1783.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
On CPython2 POSIX platforms ensure ``syconfig.get_makefile_filename`` exists within the virtual environment (this is used by some c-extension based libraries - e.g. numpy - for building) - by :user:`gaborbernat`.
23 changes: 18 additions & 5 deletions src/virtualenv/create/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ def run():
result["sys"]["fs_encoding"] = sys.getfilesystemencoding()
result["sys"]["io_encoding"] = getattr(sys.stdout, "encoding", None)
result["version"] = sys.version

try:
import sysconfig

result["makefile_filename"] = encode_path(sysconfig.get_makefile_filename())
except ImportError:
pass

import os # landmark

result["os"] = repr(os)
Expand Down Expand Up @@ -84,11 +92,16 @@ def run():
import json

result["json"] = repr(json)
print(json.dumps(result, indent=2))
except (ImportError, ValueError, TypeError) as exception: # pragma: no cover
result["json"] = repr(exception) # pragma: no cover
print(repr(result)) # pragma: no cover
raise SystemExit(1) # pragma: no cover
except ImportError as exception:
result["json"] = repr(exception)
else:
try:
content = json.dumps(result, indent=2)
sys.stdout.write(content)
except (ValueError, TypeError) as exception: # pragma: no cover
sys.stderr.write(repr(exception))
sys.stdout.write(repr(result)) # pragma: no cover
raise SystemExit(1) # pragma: no cover


if __name__ == "__main__":
Expand Down
18 changes: 17 additions & 1 deletion src/virtualenv/create/via_global_ref/builtin/cpython/cpython2.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,23 @@ def ensure_directories(self):
return dirs


class CPython2Posix(CPython2, CPythonPosix):
@add_metaclass(abc.ABCMeta)
class CPython2PosixBase(CPython2, CPythonPosix):
"""common to macOs framework builds and other posix CPython2"""

@classmethod
def sources(cls, interpreter):
for src in super(CPython2PosixBase, cls).sources(interpreter):
yield src

# check if the makefile exists and if so make it available under the virtual environment
make_file = Path(interpreter.sysconfig["makefile_filename"])
if make_file.exists() and str(make_file).startswith(interpreter.prefix):
under_prefix = make_file.relative_to(Path(interpreter.prefix))
yield PathRefToDest(make_file, dest=lambda self, s: self.dest / under_prefix)


class CPython2Posix(CPython2PosixBase):
"""CPython 2 on POSIX (excluding macOs framework builds)"""

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from virtualenv.util.six import ensure_text

from .common import CPython, CPythonPosix, is_mac_os_framework
from .cpython2 import CPython2
from .cpython2 import CPython2PosixBase
from .cpython3 import CPython3


Expand Down Expand Up @@ -65,7 +65,7 @@ def image_ref(cls, interpreter):
raise NotImplementedError


class CPython2macOsFramework(CPythonmacOsFramework, CPython2, CPythonPosix):
class CPython2macOsFramework(CPythonmacOsFramework, CPython2PosixBase):
@classmethod
def image_ref(cls, interpreter):
return Path(interpreter.prefix) / "Python"
Expand Down
2 changes: 2 additions & 0 deletions src/virtualenv/create/via_global_ref/builtin/ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ def run(self, creator, symlinks):
dest = self.dest(creator, self.src)
method = self.method(symlinks)
dest_iterable = dest if isinstance(dest, list) else (dest,)
if not dest.parent.exists():
dest.parent.mkdir(parents=True, exist_ok=True)
for dst in dest_iterable:
method(self.src, dst)

Expand Down
9 changes: 9 additions & 0 deletions src/virtualenv/discovery/py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ def abs_path(v):
self.stdout_encoding = u(getattr(sys.stdout, "encoding", None))

self.sysconfig_paths = {u(i): u(sysconfig.get_path(i, expand=False)) for i in sysconfig.get_path_names()}

self.sysconfig = {
u(k): u(v)
for k, v in [ # a list of content to store from sysconfig
("makefile_filename", sysconfig.get_makefile_filename()),
]
if k is not None
}

config_var_keys = set()
for element in self.sysconfig_paths.values():
for k in _CONF_VAR_RE.findall(element):
Expand Down
27 changes: 17 additions & 10 deletions tests/unit/create/test_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from virtualenv.__main__ import run, run_with_catch
from virtualenv.create.creator import DEBUG_SCRIPT, Creator, get_env_debug_info
from virtualenv.create.via_global_ref.builtin.cpython.cpython2 import CPython2PosixBase
from virtualenv.create.via_global_ref.builtin.cpython.cpython3 import CPython3Posix
from virtualenv.create.via_global_ref.builtin.python2.python2 import Python2
from virtualenv.discovery.builtin import get_interpreter
Expand Down Expand Up @@ -145,18 +146,20 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special
if isolated == "global":
cmd.append("--system-site-packages")
result = cli_run(cmd)
creator = result.creator
coverage_env()
if IS_PYPY:
# pypy cleans up file descriptors periodically so our (many) subprocess calls impact file descriptor limits
# force a close of these on system where the limit is low-ish (e.g. MacOS 256)
gc.collect()
purelib = result.creator.purelib
purelib = creator.purelib
patch_files = {purelib / "{}.{}".format("_virtualenv", i) for i in ("py", "pyc", "pth")}
patch_files.add(purelib / "__pycache__")
content = set(result.creator.purelib.iterdir()) - patch_files
content = set(creator.purelib.iterdir()) - patch_files
assert not content, "\n".join(ensure_text(str(i)) for i in content)
assert result.creator.env_name == ensure_text(dest.name)
debug = result.creator.debug
assert creator.env_name == ensure_text(dest.name)
debug = creator.debug
assert "exception" not in debug, "{}\n{}\n{}".format(debug.get("exception"), debug.get("out"), debug.get("err"))
sys_path = cleanup_sys_path(debug["sys"]["path"])
system_sys_path = cleanup_sys_path(system["sys"]["path"])
our_paths = set(sys_path) - set(system_sys_path)
Expand Down Expand Up @@ -205,28 +208,32 @@ def list_to_str(iterable):
# for venv some repackaging does not includes the pythonx.y
exes = exes[:-1]
for exe in exes:
exe_path = result.creator.bin_dir / exe
assert exe_path.exists(), "\n".join(str(i) for i in result.creator.bin_dir.iterdir())
exe_path = creator.bin_dir / exe
assert exe_path.exists(), "\n".join(str(i) for i in creator.bin_dir.iterdir())
if not exe_path.is_symlink(): # option 1: a real file
continue # it was a file
link = os.readlink(str(exe_path))
if not os.path.isabs(link): # option 2: a relative symlink
continue
# option 3: an absolute symlink, should point outside the venv
assert not link.startswith(str(result.creator.dest))
assert not link.startswith(str(creator.dest))

if IS_WIN and CURRENT.implementation == "CPython":
python_w = result.creator.exe.parent / "pythonw.exe"
python_w = creator.exe.parent / "pythonw.exe"
assert python_w.exists()
assert python_w.read_bytes() != result.creator.exe.read_bytes()
assert python_w.read_bytes() != creator.exe.read_bytes()

if CPython3Posix.pyvenv_launch_patch_active(PythonInfo.from_exe(python)) and creator_key != "venv":
result = subprocess.check_output(
[str(result.creator.exe), "-c", 'import os; print(os.environ.get("__PYVENV_LAUNCHER__"))'],
[str(creator.exe), "-c", 'import os; print(os.environ.get("__PYVENV_LAUNCHER__"))'],
universal_newlines=True,
).strip()
assert result == "None"

if isinstance(creator, CPython2PosixBase):
make_file = debug["makefile_filename"]
assert os.path.exists(make_file)


@pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv")
def test_venv_fails_not_inline(tmp_path, capsys, mocker):
Expand Down