diff --git a/docs/changelog/1663.bugfix.rst b/docs/changelog/1663.bugfix.rst
new file mode 100644
index 000000000..9a6f779bd
--- /dev/null
+++ b/docs/changelog/1663.bugfix.rst
@@ -0,0 +1 @@
+Support Python 3 Framework distributed via XCode in macOs Catalina and before - by :user:`gaborbernat`.
diff --git a/docs/changelog/1709.bugfix.rst b/docs/changelog/1709.bugfix.rst
new file mode 100644
index 000000000..588641993
--- /dev/null
+++ b/docs/changelog/1709.bugfix.rst
@@ -0,0 +1,2 @@
+Fix Windows Store Python support, do not allow creation via symlink as that's not going to work by design
+- by :user:`gaborbernat`.
diff --git a/docs/changelog/1716.feature.rst b/docs/changelog/1716.feature.rst
new file mode 100644
index 000000000..674b65daa
--- /dev/null
+++ b/docs/changelog/1716.feature.rst
@@ -0,0 +1,5 @@
+Improve error message when the host python does not satisfy invariants needed to create virtual environments (now we
+print which host files are incompatible/missing and for which creators when no supported creator can be matched, however
+we found creators that can describe the given Python interpreter - will still print no supported creator for Jython,
+but print exactly what host files do not allow creation of virtual environments in case of CPython/PyPy)
+- by :user:`gaborbernat`.
diff --git a/docs/installation.rst b/docs/installation.rst
index dffd3f5fa..bf319cdcb 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -116,8 +116,7 @@ In case of macOs we support:
Windows
~~~~~~~
- Installations from `python.org `_
-- Windows Store Python - note only `version 3.8+ `_ (``3.7``
- was marked experimental and contains many bugs that would make it very hard for us to support it)
+- Windows Store Python - note only `version 3.7+ `_
Packaging variants
~~~~~~~~~~~~~~~~~~
diff --git a/docs/render_cli.py b/docs/render_cli.py
index 88549ec3b..568235d95 100644
--- a/docs/render_cli.py
+++ b/docs/render_cli.py
@@ -76,12 +76,16 @@ def a(*args, **kwargs):
return True
elif key == "creator":
if name == "venv":
- from virtualenv.create.via_global_ref.venv import Meta
+ from virtualenv.create.via_global_ref.venv import ViaGlobalRefMeta
- return Meta(True, True)
- from virtualenv.create.via_global_ref.builtin.via_global_self_do import Meta
+ meta = ViaGlobalRefMeta()
+ meta.symlink_error = None
+ return meta
+ from virtualenv.create.via_global_ref.builtin.via_global_self_do import BuiltinViaGlobalRefMeta
- return Meta([], True, True)
+ meta = BuiltinViaGlobalRefMeta()
+ meta.symlink_error = None
+ return meta
raise RuntimeError
setattr(class_n, func_name, a)
diff --git a/src/virtualenv/__main__.py b/src/virtualenv/__main__.py
index ae85c680b..edc7f9512 100644
--- a/src/virtualenv/__main__.py
+++ b/src/virtualenv/__main__.py
@@ -20,7 +20,7 @@ def run(args=None, options=None):
session = cli_run(args, options)
logging.warning(LogSession(session, start))
except ProcessCallFailed as exception:
- print("subprocess call failed for {}".format(exception.cmd))
+ print("subprocess call failed for {} with code {}".format(exception.cmd, exception.code))
print(exception.out, file=sys.stdout, end="")
print(exception.err, file=sys.stderr, end="")
raise SystemExit(exception.code)
diff --git a/src/virtualenv/create/creator.py b/src/virtualenv/create/creator.py
index 4c920dc64..d4a2b9360 100644
--- a/src/virtualenv/create/creator.py
+++ b/src/virtualenv/create/creator.py
@@ -24,6 +24,11 @@
DEBUG_SCRIPT = HERE / "debug.py"
+class CreatorMeta(object):
+ def __init__(self):
+ self.error = None
+
+
@add_metaclass(ABCMeta)
class Creator(object):
"""A class that given a python Interpreter creates a virtual environment"""
diff --git a/src/virtualenv/create/via_global_ref/api.py b/src/virtualenv/create/via_global_ref/api.py
index c01301ff3..aa864c83e 100644
--- a/src/virtualenv/create/via_global_ref/api.py
+++ b/src/virtualenv/create/via_global_ref/api.py
@@ -6,10 +6,28 @@
from six import add_metaclass
+from virtualenv.info import fs_supports_symlink
from virtualenv.util.path import Path
from virtualenv.util.zipapp import ensure_file_on_disk
-from ..creator import Creator
+from ..creator import Creator, CreatorMeta
+
+
+class ViaGlobalRefMeta(CreatorMeta):
+ def __init__(self):
+ super(ViaGlobalRefMeta, self).__init__()
+ self.copy_error = None
+ self.symlink_error = None
+ if not fs_supports_symlink():
+ self.symlink = "the filesystem does not supports symlink"
+
+ @property
+ def can_copy(self):
+ return not self.copy_error
+
+ @property
+ def can_symlink(self):
+ return not self.symlink_error
@add_metaclass(ABCMeta)
diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
index 5571520d2..589544c1e 100644
--- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
+++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py
@@ -6,6 +6,7 @@
from virtualenv.create.describe import Python3Supports
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest
+from virtualenv.create.via_global_ref.store import is_store_python
from virtualenv.util.path import Path
from .common import CPython, CPythonPosix, CPythonWindows, is_mac_os_framework
@@ -25,6 +26,12 @@ def can_describe(cls, interpreter):
class CPython3Windows(CPythonWindows, CPython3):
""""""
+ @classmethod
+ def setup_meta(cls, interpreter):
+ if is_store_python(interpreter): # store python is not supported here
+ return None
+ return super(CPython3Windows, cls).setup_meta(interpreter)
+
@classmethod
def sources(cls, interpreter):
for src in super(CPython3Windows, cls).sources(interpreter):
diff --git a/src/virtualenv/create/via_global_ref/builtin/ref.py b/src/virtualenv/create/via_global_ref/builtin/ref.py
index 15a644aa7..902299496 100644
--- a/src/virtualenv/create/via_global_ref/builtin/ref.py
+++ b/src/virtualenv/create/via_global_ref/builtin/ref.py
@@ -28,7 +28,10 @@ def __init__(self, src, must_symlink, must_copy):
self.must_symlink = must_symlink
self.must_copy = must_copy
self.src = src
- self.exists = src.exists()
+ try:
+ self.exists = src.exists()
+ except OSError:
+ self.exists = False
self._can_read = None if self.exists else False
self._can_copy = None if self.exists else False
self._can_symlink = None if self.exists else False
@@ -141,7 +144,8 @@ def run(self, creator, symlinks):
dest = bin_dir / self.base
method = self.method(symlinks)
method(self.src, dest)
- make_exe(dest)
+ if not symlinks:
+ make_exe(dest)
for extra in self.aliases:
link_file = bin_dir / extra
if link_file.exists():
@@ -150,4 +154,8 @@ def run(self, creator, symlinks):
link_file.symlink_to(self.base)
else:
copy(self.src, link_file)
- make_exe(link_file)
+ if not symlinks:
+ make_exe(link_file)
+
+ def __repr__(self):
+ return "{}(src={}, alias={})".format(self.__class__.__name__, self.src, self.aliases)
diff --git a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
index 922a74dad..97bf533d8 100644
--- a/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
+++ b/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py
@@ -1,19 +1,20 @@
from __future__ import absolute_import, unicode_literals
-import logging
from abc import ABCMeta
-from collections import namedtuple
from six import add_metaclass
from virtualenv.create.via_global_ref.builtin.ref import ExePathRefToDest
-from virtualenv.info import fs_supports_symlink
from virtualenv.util.path import ensure_dir
-from ..api import ViaGlobalRefApi
+from ..api import ViaGlobalRefApi, ViaGlobalRefMeta
from .builtin_way import VirtualenvBuiltin
-Meta = namedtuple("Meta", ["sources", "can_copy", "can_symlink"])
+
+class BuiltinViaGlobalRefMeta(ViaGlobalRefMeta):
+ def __init__(self):
+ super(BuiltinViaGlobalRefMeta, self).__init__()
+ self.sources = []
@add_metaclass(ABCMeta)
@@ -27,27 +28,30 @@ def can_create(cls, interpreter):
"""By default all built-in methods assume that if we can describe it we can create it"""
# first we must be able to describe it
if cls.can_describe(interpreter):
- sources = []
- can_copy = True
- can_symlink = fs_supports_symlink()
- for src in cls.sources(interpreter):
- if src.exists:
- if can_copy and not src.can_copy:
- can_copy = False
- logging.debug("%s cannot copy %s", cls.__name__, src)
- if can_symlink and not src.can_symlink:
- can_symlink = False
- logging.debug("%s cannot symlink %s", cls.__name__, src)
- if not (can_copy or can_symlink):
+ meta = cls.setup_meta(interpreter)
+ if meta is not None and meta:
+ for src in cls.sources(interpreter):
+ if src.exists:
+ if meta.can_copy and not src.can_copy:
+ meta.copy_error = "cannot copy {}".format(src)
+ if meta.can_symlink and not src.can_symlink:
+ meta.symlink_error = "cannot symlink {}".format(src)
+ if not meta.can_copy and not meta.can_symlink:
+ meta.error = "neither copy or symlink supported: {}".format(
+ meta.copy_error, meta.symlink_error
+ )
+ else:
+ meta.error = "missing required file {}".format(src)
+ if meta.error:
break
- else:
- logging.debug("%s missing %s", cls.__name__, src)
- break
- sources.append(src)
- else:
- return Meta(sources, can_copy, can_symlink)
+ meta.sources.append(src)
+ return meta
return None
+ @classmethod
+ def setup_meta(cls, interpreter):
+ return BuiltinViaGlobalRefMeta()
+
@classmethod
def sources(cls, interpreter):
is_py2 = interpreter.version_info.major == 2
diff --git a/src/virtualenv/create/via_global_ref/store.py b/src/virtualenv/create/via_global_ref/store.py
new file mode 100644
index 000000000..55d941347
--- /dev/null
+++ b/src/virtualenv/create/via_global_ref/store.py
@@ -0,0 +1,21 @@
+from virtualenv.util.path import Path
+
+
+def handle_store_python(meta, interpreter):
+ if is_store_python(interpreter):
+ meta.symlink_error = "Windows Store Python does not support virtual environments via symlink"
+ return meta
+
+
+def is_store_python(interpreter):
+ parts = Path(interpreter.system_executable).parts
+ return (
+ len(parts) > 4
+ and parts[-4] == "Microsoft"
+ and parts[-3] == "WindowsApps"
+ and parts[-2].startswith("PythonSoftwareFoundation.Python.3.")
+ and parts[-1].startswith("python")
+ )
+
+
+__all__ = ("handle_store_python", "is_store_python")
diff --git a/src/virtualenv/create/via_global_ref/venv.py b/src/virtualenv/create/via_global_ref/venv.py
index 5b774ef2d..88e74e36a 100644
--- a/src/virtualenv/create/via_global_ref/venv.py
+++ b/src/virtualenv/create/via_global_ref/venv.py
@@ -1,18 +1,15 @@
from __future__ import absolute_import, unicode_literals
import logging
-from collections import namedtuple
from copy import copy
+from virtualenv.create.via_global_ref.store import handle_store_python
from virtualenv.discovery.py_info import PythonInfo
from virtualenv.error import ProcessCallFailed
-from virtualenv.info import fs_supports_symlink
from virtualenv.util.path import ensure_dir
from virtualenv.util.subprocess import run_cmd
-from .api import ViaGlobalRefApi
-
-Meta = namedtuple("Meta", ["can_symlink", "can_copy"])
+from .api import ViaGlobalRefApi, ViaGlobalRefMeta
class Venv(ViaGlobalRefApi):
@@ -30,7 +27,10 @@ def _args(self):
@classmethod
def can_create(cls, interpreter):
if interpreter.has_venv:
- return Meta(can_symlink=fs_supports_symlink(), can_copy=True)
+ meta = ViaGlobalRefMeta()
+ if interpreter.platform == "win32" and interpreter.version_info.major == 3:
+ meta = handle_store_python(meta, interpreter)
+ return meta
return None
def create(self):
diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py
index 35734ce5c..24985555a 100644
--- a/src/virtualenv/discovery/builtin.py
+++ b/src/virtualenv/discovery/builtin.py
@@ -55,22 +55,29 @@ def get_interpreter(key, app_data=None):
def propose_interpreters(spec, app_data):
- # 1. if it's an absolute path and exists, use that
- if spec.is_abs and os.path.exists(spec.path):
- yield PythonInfo.from_exe(spec.path, app_data), True
-
- # 2. try with the current
- yield PythonInfo.current_system(app_data), True
-
- # 3. otherwise fallback to platform default logic
- if IS_WIN:
- from .windows import propose_interpreters
+ # 1. if it's a path and exists
+ if spec.path is not None:
+ try:
+ os.lstat(spec.path) # Windows Store Python does not work with os.path.exists, but does for os.lstat
+ except OSError:
+ if spec.is_abs:
+ raise
+ else:
+ yield PythonInfo.from_exe(os.path.abspath(spec.path), app_data), True
+ if spec.is_abs:
+ return
+ else:
+ # 2. otherwise try with the current
+ yield PythonInfo.current_system(app_data), True
- for interpreter in propose_interpreters(spec, app_data):
- yield interpreter, True
+ # 3. otherwise fallback to platform default logic
+ if IS_WIN:
+ from .windows import propose_interpreters
+ for interpreter in propose_interpreters(spec, app_data):
+ yield interpreter, True
+ # finally just find on path, the path order matters (as the candidates are less easy to control by end user)
paths = get_paths()
- # find on path, the path order matters (as the candidates are less easy to control by end user)
tested_exes = set()
for pos, path in enumerate(paths):
path = ensure_text(path)
diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py
index e2f1d54fd..baed2e38e 100644
--- a/src/virtualenv/discovery/cached_py_info.py
+++ b/src/virtualenv/discovery/cached_py_info.py
@@ -65,11 +65,14 @@ def _get_via_file_cache(cls, py_info_cache, app_data, resolved_path, exe):
key = sha256(str(resolved_path).encode("utf-8") if PY3 else str(resolved_path)).hexdigest()
py_info = None
resolved_path_text = ensure_text(str(resolved_path))
- resolved_path_modified_timestamp = resolved_path.stat().st_mtime
+ try:
+ resolved_path_modified_timestamp = resolved_path.stat().st_mtime
+ except OSError:
+ resolved_path_modified_timestamp = -1
data_file = py_info_cache / "{}.json".format(key)
with py_info_cache.lock_for_key(key):
data_file_path = data_file.path
- if data_file_path.exists(): # if exists and matches load
+ if data_file_path.exists() and resolved_path_modified_timestamp != 1: # if exists and matches load
try:
data = json.loads(data_file_path.read_text())
if data["path"] == resolved_path_text and data["st_mtime"] == resolved_path_modified_timestamp:
diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py
index 98c0b978a..e94528bcc 100644
--- a/src/virtualenv/discovery/py_info.py
+++ b/src/virtualenv/discovery/py_info.py
@@ -419,7 +419,7 @@ def _find_possible_folders(self, inside_folder):
# or at root level
candidate_folder[inside_folder] = None
- return list(candidate_folder.keys())
+ return list(i for i in candidate_folder.keys() if os.path.exists(i))
def _find_possible_exe_names(self):
name_candidate = OrderedDict()
diff --git a/src/virtualenv/discovery/py_spec.py b/src/virtualenv/discovery/py_spec.py
index 9b9f56e51..71712d1ec 100644
--- a/src/virtualenv/discovery/py_spec.py
+++ b/src/virtualenv/discovery/py_spec.py
@@ -64,11 +64,13 @@ def _int_or_none(val):
arch = _int_or_none(groups["arch"])
if not ok:
- path = os.path.abspath(string_spec)
+ path = string_spec
return cls(string_spec, impl, major, minor, micro, arch, path)
def generate_names(self):
+ if self.implementation is None:
+ return
impls = OrderedDict()
if self.implementation:
# first consider implementation as it is
diff --git a/src/virtualenv/run/plugin/creators.py b/src/virtualenv/run/plugin/creators.py
index cbf0a5d22..4c349cdb6 100644
--- a/src/virtualenv/run/plugin/creators.py
+++ b/src/virtualenv/run/plugin/creators.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from collections import OrderedDict, namedtuple
+from collections import OrderedDict, defaultdict, namedtuple
from virtualenv.create.describe import Describe
from virtualenv.create.via_global_ref.builtin.builtin_way import VirtualenvBuiltin
@@ -13,26 +13,37 @@
class CreatorSelector(ComponentBuilder):
def __init__(self, interpreter, parser):
creators, self.key_to_meta, self.describe, self.builtin_key = self.for_interpreter(interpreter)
- if not creators:
- raise RuntimeError("No virtualenv implementation for {}".format(interpreter))
super(CreatorSelector, self).__init__(interpreter, parser, "creator", creators)
@classmethod
def for_interpreter(cls, interpreter):
key_to_class, key_to_meta, builtin_key, describe = OrderedDict(), {}, None, None
+ errored = defaultdict(list)
for key, creator_class in cls.options("virtualenv.create").items():
if key == "builtin":
raise RuntimeError("builtin creator is a reserved name")
meta = creator_class.can_create(interpreter)
if meta:
- if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin):
- builtin_key = key
- key_to_class["builtin"] = creator_class
- key_to_meta["builtin"] = meta
- key_to_class[key] = creator_class
- key_to_meta[key] = meta
+ if meta.error:
+ errored[meta.error].append(creator_class)
+ else:
+ if "builtin" not in key_to_class and issubclass(creator_class, VirtualenvBuiltin):
+ builtin_key = key
+ key_to_class["builtin"] = creator_class
+ key_to_meta["builtin"] = meta
+ key_to_class[key] = creator_class
+ key_to_meta[key] = meta
if describe is None and issubclass(creator_class, Describe) and creator_class.can_describe(interpreter):
describe = creator_class
+ if not key_to_meta:
+ if errored:
+ raise RuntimeError(
+ "\n".join(
+ "{} for creators {}".format(k, ", ".join(i.__name__ for i in v)) for k, v in errored.items()
+ )
+ )
+ else:
+ raise RuntimeError("No virtualenv implementation for {}".format(interpreter))
return CreatorInfo(
key_to_class=key_to_class, key_to_meta=key_to_meta, describe=describe, builtin_key=builtin_key
)
diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py
index cf30f2bc3..4d86ffd95 100644
--- a/tests/unit/create/test_creator.py
+++ b/tests/unit/create/test_creator.py
@@ -21,7 +21,7 @@
from virtualenv.create.creator import DEBUG_SCRIPT, Creator, get_env_debug_info
from virtualenv.discovery.builtin import get_interpreter
from virtualenv.discovery.py_info import PythonInfo
-from virtualenv.info import IS_PYPY, IS_WIN, PY2, PY3, fs_is_case_sensitive, fs_supports_symlink
+from virtualenv.info import IS_PYPY, IS_WIN, PY2, PY3, fs_is_case_sensitive
from virtualenv.pyenv_cfg import PyEnvCfg
from virtualenv.run import cli_run, session_via_cli
from virtualenv.util.path import Path
@@ -88,6 +88,13 @@ def system(session_app_data):
CURRENT_CREATORS = list(i for i in CURRENT.creators().key_to_class.keys() if i != "builtin")
+CREATE_METHODS = []
+for k, v in CURRENT.creators().key_to_meta.items():
+ if k in CURRENT_CREATORS:
+ if v.can_copy:
+ CREATE_METHODS.append((k, "copies"))
+ if v.can_symlink:
+ CREATE_METHODS.append((k, "symlinks"))
_VENV_BUG_ON = (
IS_PYPY
and CURRENT.version_info[0:3] == (3, 6, 9)
@@ -97,7 +104,7 @@ def system(session_app_data):
@pytest.mark.parametrize(
- "creator, method, isolated",
+ "creator, isolated",
[
pytest.param(
*i,
@@ -106,15 +113,15 @@ def system(session_app_data):
strict=True,
)
)
- if _VENV_BUG_ON and i[0] == "venv" and i[1] == "copies"
+ if _VENV_BUG_ON and i[0][0] == "venv" and i[0][1] == "copies"
else i
- for i in product(
- CURRENT_CREATORS, (["copies"] + (["symlinks"] if fs_supports_symlink() else [])), ["isolated", "global"]
- )
+ for i in product(CREATE_METHODS, ["isolated", "global"])
],
+ ids=lambda i: "-".join(i) if isinstance(i, tuple) else i,
)
-def test_create_no_seed(python, creator, isolated, system, coverage_env, special_name_dir, method):
+def test_create_no_seed(python, creator, isolated, system, coverage_env, special_name_dir):
dest = special_name_dir
+ creator_key, method = creator
cmd = [
"-v",
"-v",
@@ -125,7 +132,7 @@ def test_create_no_seed(python, creator, isolated, system, coverage_env, special
"--activators",
"",
"--creator",
- creator,
+ creator_key,
"--{}".format(method),
]
if isolated == "global":
@@ -188,11 +195,11 @@ def list_to_str(iterable):
else:
exes = ("python", "python{}".format(*sys.version_info), "python{}.{}".format(*sys.version_info))
# pypy3<=7.3: https://bitbucket.org/pypy/pypy/pull-requests/697
- if IS_PYPY and CURRENT.pypy_version_info[:3] <= [7, 3, 0] and creator == "venv":
+ if IS_PYPY and creator_key == "venv":
exes = exes[:-1]
for exe in exes:
exe_path = result.creator.bin_dir / exe
- assert exe_path.exists()
+ assert exe_path.exists(), "\n".join(str(i) for i in result.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))
diff --git a/tests/unit/discovery/py_info/test_py_info.py b/tests/unit/discovery/py_info/test_py_info.py
index ad9d399e2..e673fcdd2 100644
--- a/tests/unit/discovery/py_info/test_py_info.py
+++ b/tests/unit/discovery/py_info/test_py_info.py
@@ -15,6 +15,7 @@
from virtualenv.discovery.py_info import PythonInfo, VersionInfo
from virtualenv.discovery.py_spec import PythonSpec
from virtualenv.info import fs_supports_symlink
+from virtualenv.util.path import Path
CURRENT = PythonInfo.current_system()
@@ -149,6 +150,9 @@ def test_py_info_cached_symlink(mocker, tmp_path, session_app_data):
new_exe = tmp_path / "a"
new_exe.symlink_to(sys.executable)
+ pyvenv = Path(sys.executable).parents[1] / "pyvenv.cfg"
+ if pyvenv.exists():
+ (tmp_path / pyvenv.name).write_text(pyvenv.read_text())
new_exe_str = str(new_exe)
second_result = PythonInfo.from_exe(new_exe_str, session_app_data)
assert second_result.executable == new_exe_str
diff --git a/tests/unit/discovery/py_info/test_py_info_exe_based_of.py b/tests/unit/discovery/py_info/test_py_info_exe_based_of.py
index 9b87be02c..3bc3e7939 100644
--- a/tests/unit/discovery/py_info/test_py_info_exe_based_of.py
+++ b/tests/unit/discovery/py_info/test_py_info_exe_based_of.py
@@ -7,6 +7,7 @@
from virtualenv.discovery.py_info import EXTENSIONS, PythonInfo
from virtualenv.info import IS_WIN, fs_is_case_sensitive, fs_supports_symlink
+from virtualenv.util.path import Path
CURRENT = PythonInfo.current()
@@ -31,6 +32,9 @@ def test_discover_ok(tmp_path, monkeypatch, suffix, impl, version, arch, into, c
folder.mkdir(parents=True, exist_ok=True)
dest = folder / "{}{}".format(impl, version, arch, suffix)
os.symlink(CURRENT.executable, str(dest))
+ pyvenv = Path(CURRENT.executable).parents[1] / "pyvenv.cfg"
+ if pyvenv.exists():
+ (folder / pyvenv.name).write_text(pyvenv.read_text())
inside_folder = str(tmp_path)
base = CURRENT.discover_exe(session_app_data, inside_folder)
found = base.executable
diff --git a/tests/unit/discovery/test_discovery.py b/tests/unit/discovery/test_discovery.py
index 260052d4b..275623a31 100644
--- a/tests/unit/discovery/test_discovery.py
+++ b/tests/unit/discovery/test_discovery.py
@@ -10,6 +10,7 @@
from virtualenv.discovery.builtin import get_interpreter
from virtualenv.discovery.py_info import PythonInfo
from virtualenv.info import fs_supports_symlink
+from virtualenv.util.path import Path
from virtualenv.util.six import ensure_text
@@ -25,10 +26,14 @@ def test_discovery_via_path(monkeypatch, case, special_name_dir, caplog, session
elif case == "upper":
name = name.upper()
exe_name = "{}{}{}".format(name, current.version_info.major, ".exe" if sys.platform == "win32" else "")
- special_name_dir.mkdir()
- executable = special_name_dir / exe_name
+ target = special_name_dir / current.distutils_install["scripts"]
+ target.mkdir(parents=True)
+ executable = target / exe_name
os.symlink(sys.executable, ensure_text(str(executable)))
- new_path = os.pathsep.join([str(special_name_dir)] + os.environ.get(str("PATH"), str("")).split(os.pathsep))
+ pyvenv_cfg = Path(sys.executable).parents[1] / "pyvenv.cfg"
+ if pyvenv_cfg.exists():
+ (target / pyvenv_cfg.name).write_bytes(pyvenv_cfg.read_bytes())
+ new_path = os.pathsep.join([str(target)] + os.environ.get(str("PATH"), str("")).split(os.pathsep))
monkeypatch.setenv(str("PATH"), new_path)
interpreter = get_interpreter(core)
diff --git a/tests/unit/discovery/test_py_spec.py b/tests/unit/discovery/test_py_spec.py
index f4496e980..93473b53f 100644
--- a/tests/unit/discovery/test_py_spec.py
+++ b/tests/unit/discovery/test_py_spec.py
@@ -1,7 +1,6 @@
from __future__ import absolute_import, unicode_literals
import itertools
-import os
import sys
from copy import copy
@@ -15,7 +14,7 @@ def test_bad_py_spec():
spec = PythonSpec.from_string_spec(text)
assert text in repr(spec)
assert spec.str_spec == text
- assert spec.path == os.path.abspath(text)
+ assert spec.path == text
content = vars(spec)
del content[str("str_spec")]
del content[str("path")]
@@ -112,4 +111,4 @@ def test_relative_spec(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
a_relative_path = str((tmp_path / "a" / "b").relative_to(tmp_path))
spec = PythonSpec.from_string_spec(a_relative_path)
- assert spec.path == os.path.abspath(str(tmp_path / a_relative_path))
+ assert spec.path == a_relative_path