summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2020-01-18 17:07:49 +0000
committerGitHub <noreply@github.com>2020-01-18 17:07:49 +0000
commit8d6af57d76edcf425beab6d53d4c14f1e49f7ca5 (patch)
treede400611862464320c6a45a932e27c2bac0c63ea
parent8f4128879b26921af2ec0f0f3f690ed2324bfb04 (diff)
downloadvirtualenv-8d6af57d76edcf425beab6d53d4c14f1e49f7ca5.tar.gz
CentOs and Fedora support (#1500)
* CentOs support Instead of hard coding patterns and guessing let's ask the host python via the sysconfig. Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * add isolated test Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * fixes for Fedora Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
-rw-r--r--src/virtualenv/activation/python/__init__.py2
-rw-r--r--src/virtualenv/activation/via_template.py2
-rw-r--r--src/virtualenv/info.py2
-rw-r--r--src/virtualenv/interpreters/create/builtin_way.py16
-rw-r--r--src/virtualenv/interpreters/create/cpython/common.py24
-rw-r--r--src/virtualenv/interpreters/create/creator.py50
-rw-r--r--src/virtualenv/interpreters/create/debug.py8
-rw-r--r--src/virtualenv/interpreters/create/pypy/common.py4
-rw-r--r--src/virtualenv/interpreters/create/pypy/pypy2.py25
-rw-r--r--src/virtualenv/interpreters/create/pypy/pypy3.py43
-rw-r--r--src/virtualenv/interpreters/create/venv.py38
-rw-r--r--src/virtualenv/interpreters/create/via_global_ref/python2.py14
-rw-r--r--src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py22
-rw-r--r--src/virtualenv/interpreters/discovery/py_info.py84
-rw-r--r--src/virtualenv/pyenv_cfg.py3
-rw-r--r--src/virtualenv/seed/via_app_data/pip_install/base.py9
-rw-r--r--src/virtualenv/util/path/_permission.py2
-rw-r--r--src/virtualenv/util/path/_sync.py4
-rw-r--r--src/virtualenv/util/subprocess/_win_subprocess.py1
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/unit/activation/conftest.py2
-rw-r--r--tests/unit/activation/test_python_activator.py30
-rw-r--r--tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py8
-rw-r--r--tests/unit/interpreters/boostrap/test_pip_invoke.py2
-rw-r--r--tests/unit/interpreters/create/test_creator.py29
25 files changed, 207 insertions, 219 deletions
diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py
index b06af78..8d7e80e 100644
--- a/src/virtualenv/activation/python/__init__.py
+++ b/src/virtualenv/activation/python/__init__.py
@@ -14,6 +14,6 @@ class PythonActivator(ViaTemplateActivator):
def replacements(self, creator, dest_folder):
replacements = super(PythonActivator, self).replacements(creator, dest_folder)
- site_dump = json.dumps([os.path.relpath(str(i), str(dest_folder)) for i in creator.site_packages], indent=2)
+ site_dump = json.dumps(list({os.path.relpath(str(i), str(dest_folder)) for i in creator.libs}), indent=2)
replacements.update({"__SITE_PACKAGES__": site_dump})
return replacements
diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py
index f707897..442776a 100644
--- a/src/virtualenv/activation/via_template.py
+++ b/src/virtualenv/activation/via_template.py
@@ -31,7 +31,7 @@ class ViaTemplateActivator(Activator):
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
"__VIRTUAL_ENV__": six.ensure_text(str(creator.dest_dir)),
"__VIRTUAL_NAME__": creator.env_name,
- "__BIN_NAME__": six.ensure_text(str(creator.bin_name)),
+ "__BIN_NAME__": six.ensure_text(str(creator.bin_dir.relative_to(creator.dest_dir))),
"__PATH_SEP__": os.pathsep,
}
diff --git a/src/virtualenv/info.py b/src/virtualenv/info.py
index 573a97f..fbb7573 100644
--- a/src/virtualenv/info.py
+++ b/src/virtualenv/info.py
@@ -45,7 +45,7 @@ def is_fs_case_sensitive():
with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file:
_FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower())
logging.debug(
- "filesystem under is %r%s case-sensitive", tmp_file.name, "" if _FS_CASE_SENSITIVE else " not"
+ "filesystem under %r is %scase-sensitive", tmp_file.name, "" if _FS_CASE_SENSITIVE else "not "
)
return _FS_CASE_SENSITIVE
diff --git a/src/virtualenv/interpreters/create/builtin_way.py b/src/virtualenv/interpreters/create/builtin_way.py
index 4134d7b..31ea9e4 100644
--- a/src/virtualenv/interpreters/create/builtin_way.py
+++ b/src/virtualenv/interpreters/create/builtin_way.py
@@ -13,22 +13,6 @@ class VirtualenvBuiltin(ViaGlobalRefApi):
"""A creator that does operations itself without delegation"""
@property
- def bin_name(self):
- raise NotImplementedError
-
- @property
- def bin_dir(self):
- return self.dest_dir / self.bin_name
-
- @property
- def lib_dir(self):
- raise NotImplementedError
-
- @property
- def site_packages(self):
- return [self.lib_dir / "site-packages"]
-
- @property
def exe_name(self):
raise NotImplementedError
diff --git a/src/virtualenv/interpreters/create/cpython/common.py b/src/virtualenv/interpreters/create/cpython/common.py
index 490b0ad..ba921b1 100644
--- a/src/virtualenv/interpreters/create/cpython/common.py
+++ b/src/virtualenv/interpreters/create/cpython/common.py
@@ -24,18 +24,6 @@ class CPython(ViaGlobalRefVirtualenvBuiltin):
class CPythonPosix(CPython, PosixSupports):
"""Create a CPython virtual environment on POSIX platforms"""
- @property
- def bin_name(self):
- return "bin"
-
- @property
- def lib_name(self):
- return "lib"
-
- @property
- def lib_base(self):
- return Path(self.lib_name) / self.interpreter.python_name
-
def link_exe(self):
host = Path(self.interpreter.system_executable)
major, minor = self.interpreter.version_info.major, self.interpreter.version_info.minor
@@ -44,18 +32,6 @@ class CPythonPosix(CPython, PosixSupports):
@six.add_metaclass(abc.ABCMeta)
class CPythonWindows(CPython, WindowsSupports):
- @property
- def bin_name(self):
- return "Scripts"
-
- @property
- def lib_name(self):
- return "Lib"
-
- @property
- def lib_base(self):
- return Path(self.lib_name)
-
def link_exe(self):
host = Path(self.interpreter.system_executable)
return {p: [p.name] for p in (host.parent / n for n in ("python.exe", "pythonw.exe", host.name)) if p.exists()}
diff --git a/src/virtualenv/interpreters/create/creator.py b/src/virtualenv/interpreters/create/creator.py
index 1185b56..f0adf9e 100644
--- a/src/virtualenv/interpreters/create/creator.py
+++ b/src/virtualenv/interpreters/create/creator.py
@@ -36,6 +36,55 @@ class Creator(object):
self.clear = options.clear
self.pyenv_cfg = PyEnvCfg.from_folder(self.dest_dir)
+ self._stdlib = None
+ self._system_stdlib = None
+ self._conf_vars = None
+
+ @property
+ def bin_dir(self):
+ return self.script_dir
+
+ @property
+ def script_dir(self):
+ return self.dest_dir / Path(self.interpreter.distutils_install["scripts"])
+
+ @property
+ def purelib(self):
+ return self.dest_dir / self.interpreter.distutils_install["purelib"]
+
+ @property
+ def platlib(self):
+ return self.dest_dir / self.interpreter.distutils_install["platlib"]
+
+ @property
+ def libs(self):
+ return list(OrderedDict(((self.platlib, None), (self.purelib, None))).keys())
+
+ @property
+ def stdlib(self):
+ if self._stdlib is None:
+ self._stdlib = Path(self.interpreter.sysconfig_path("stdlib", config_var=self._config_vars))
+ return self._stdlib
+
+ @property
+ def system_stdlib(self):
+ if self._system_stdlib is None:
+ conf_vars = self._calc_config_vars(self.interpreter.system_prefix)
+ self._system_stdlib = Path(self.interpreter.sysconfig_path("stdlib", conf_vars))
+ return self._system_stdlib
+
+ @property
+ def _config_vars(self):
+ if self._conf_vars is None:
+ self._conf_vars = self._calc_config_vars(six.ensure_text(str(self.dest_dir)))
+ return self._conf_vars
+
+ def _calc_config_vars(self, to):
+ return {
+ k: (to if v.startswith(self.interpreter.prefix) else v)
+ for k, v in self.interpreter.sysconfig_config_vars.items()
+ }
+
def __repr__(self):
return six.ensure_str(self.__unicode__())
@@ -154,6 +203,7 @@ class Creator(object):
"home": self.interpreter.system_exec_prefix,
"include-system-site-packages": "true" if self.enable_system_site_package else "false",
"implementation": self.interpreter.implementation,
+ "version_info": ".".join(str(i) for i in self.interpreter.version_info),
"virtualenv": __version__,
}
diff --git a/src/virtualenv/interpreters/create/debug.py b/src/virtualenv/interpreters/create/debug.py
index e2b1585..674d1bb 100644
--- a/src/virtualenv/interpreters/create/debug.py
+++ b/src/virtualenv/interpreters/create/debug.py
@@ -66,6 +66,14 @@ def run():
except ImportError as exception: # pragma: no cover
result["datetime"] = repr(exception) # pragma: no cover
+ try:
+ # noinspection PyUnresolvedReferences
+ import math # site
+
+ result["math"] = repr(math)
+ except ImportError as exception: # pragma: no cover
+ result["math"] = repr(exception) # pragma: no cover
+
# try to print out, this will validate if other core modules are available (json in this case)
try:
import json
diff --git a/src/virtualenv/interpreters/create/pypy/common.py b/src/virtualenv/interpreters/create/pypy/common.py
index 1c98064..72dfb21 100644
--- a/src/virtualenv/interpreters/create/pypy/common.py
+++ b/src/virtualenv/interpreters/create/pypy/common.py
@@ -14,10 +14,6 @@ class PyPy(ViaGlobalRefVirtualenvBuiltin):
def supports(cls, interpreter):
return interpreter.implementation == "PyPy" and super(PyPy, cls).supports(interpreter)
- @property
- def site_packages(self):
- return [self.dest_dir / "site-packages"]
-
def link_exe(self):
host = Path(self.interpreter.system_executable)
return {host: sorted("{}{}".format(name, self.suffix) for name in self.exe_names())}
diff --git a/src/virtualenv/interpreters/create/pypy/pypy2.py b/src/virtualenv/interpreters/create/pypy/pypy2.py
index fe34cdc..b16b53d 100644
--- a/src/virtualenv/interpreters/create/pypy/pypy2.py
+++ b/src/virtualenv/interpreters/create/pypy/pypy2.py
@@ -6,7 +6,6 @@ import six
from virtualenv.interpreters.create.support import PosixSupports, WindowsSupports
from virtualenv.interpreters.create.via_global_ref.python2 import Python2
-from virtualenv.util.path import Path
from .common import PyPy
@@ -20,21 +19,17 @@ class PyPy2(PyPy, Python2):
return "pypy"
@property
- def lib_name(self):
- return "lib-python"
-
- @property
def lib_pypy(self):
return self.dest_dir / "lib_pypy"
- @property
- def lib_base(self):
- return Path(self.lib_name) / self.interpreter.version_release_str
+ def _calc_config_vars(self, to):
+ base = super(PyPy, self)._calc_config_vars(to)
+ # for some reason pypy seems to provide the wrong information for implementation_lower, fix it
+ base["implementation_lower"] = "python"
+ return base
def ensure_directories(self):
- dirs = super(PyPy, self).ensure_directories()
- dirs.add(self.lib_pypy)
- return dirs
+ return super(PyPy, self).ensure_directories() | {self.lib_pypy}
def modules(self):
return [
@@ -51,10 +46,6 @@ class PyPy2(PyPy, Python2):
class PyPy2Posix(PyPy2, PosixSupports):
"""PyPy 2 on POSIX"""
- @property
- def bin_name(self):
- return "bin"
-
def modules(self):
return super(PyPy2Posix, self).modules() + ["posixpath"]
@@ -66,10 +57,6 @@ class PyPy2Posix(PyPy2, PosixSupports):
class Pypy2Windows(PyPy2, WindowsSupports):
"""PyPy 2 on Windows"""
- @property
- def bin_name(self):
- return "Scripts"
-
def modules(self):
return super(Pypy2Windows, self).modules() + ["ntpath"]
diff --git a/src/virtualenv/interpreters/create/pypy/pypy3.py b/src/virtualenv/interpreters/create/pypy/pypy3.py
index 6b759cb..724f0fb 100644
--- a/src/virtualenv/interpreters/create/pypy/pypy3.py
+++ b/src/virtualenv/interpreters/create/pypy/pypy3.py
@@ -5,67 +5,48 @@ import abc
import six
from virtualenv.interpreters.create.support import PosixSupports, Python3Supports, WindowsSupports
-from virtualenv.util.path import Path
from .common import PyPy
@six.add_metaclass(abc.ABCMeta)
class PyPy3(PyPy, Python3Supports):
- """"""
-
@property
def exe_base(self):
return "pypy3"
+ @property
+ def stdlib(self):
+ """
+ PyPy3 seems to respect sysconfig only for the host python...
+ virtual environments purelib is instead lib/pythonx.y
+ """
+ return self.dest_dir / "lib" / "python{}".format(self.interpreter.version_release_str) / "site-packages"
+
def exe_names(self):
base = super(PyPy3, self).exe_names()
base.add("pypy")
return base
- def ensure_directories(self):
- dirs = super(PyPy, self).ensure_directories()
- dirs.add(self.lib_dir / "site-packages")
- return dirs
-
class PyPy3Posix(PyPy3, PosixSupports):
"""PyPy 2 on POSIX"""
@property
- def bin_name(self):
- return "bin"
-
- @property
- def lib_name(self):
- return "lib"
-
- @property
- def lib_base(self):
- return Path(self.lib_name) / self.interpreter.python_name
-
- @property
def _shared_libs(self):
return ["libpypy3-c.so", "libpypy3-c.dylib"]
def _shared_lib_to(self):
- return super(PyPy3, self)._shared_lib_to() + [self.dest_dir / self.lib_name]
+ return super(PyPy3, self)._shared_lib_to() + [self.stdlib.parent.parent]
class Pypy3Windows(PyPy3, WindowsSupports):
"""PyPy 2 on Windows"""
@property
- def bin_name(self):
- return "Scripts"
-
- @property
- def lib_name(self):
- return "Lib"
-
- @property
- def lib_base(self):
- return Path(self.lib_name)
+ def bin_dir(self):
+ """PyPy3 needs to fallback to pypy definition"""
+ return self.dest_dir / "Scripts"
@property
def _shared_libs(self):
diff --git a/src/virtualenv/interpreters/create/venv.py b/src/virtualenv/interpreters/create/venv.py
index afb9471..78d4622 100644
--- a/src/virtualenv/interpreters/create/venv.py
+++ b/src/virtualenv/interpreters/create/venv.py
@@ -13,10 +13,10 @@ from .via_global_ref.api import ViaGlobalRefApi
class Venv(ViaGlobalRefApi):
def __init__(self, options, interpreter):
+ self.builtin_way = options.builtin_way
super(Venv, self).__init__(options, interpreter)
self.can_be_inline = interpreter is CURRENT and interpreter.executable == interpreter.system_executable
self._context = None
- self.builtin_way = options.builtin_way
def _args(self):
return super(Venv, self)._args() + (
@@ -33,9 +33,8 @@ class Venv(ViaGlobalRefApi):
else:
self.create_via_sub_process()
# TODO: cleanup activation scripts
- if self.builtin_way is not None:
- for site_package in self.builtin_way.site_packages:
- ensure_dir(site_package)
+ for lib in self.libs:
+ ensure_dir(lib)
def create_inline(self):
from venv import EnvBuilder
@@ -66,27 +65,10 @@ class Venv(ViaGlobalRefApi):
super(Venv, self).set_pyenv_cfg()
self.pyenv_cfg.update(venv_content)
- def _proxy_builtin_way(self, key):
- if self.builtin_way is None:
- return None
- return getattr(self.builtin_way, key)
-
- @property
- def exe(self):
- return self._proxy_builtin_way("exe")
-
- @property
- def site_packages(self):
- return self._proxy_builtin_way("site_packages")
-
- @property
- def bin_dir(self):
- return self._proxy_builtin_way("bin_dir")
-
- @property
- def bin_name(self):
- return self._proxy_builtin_way("bin_name")
-
- @property
- def lib_dir(self):
- return self._proxy_builtin_way("lib_dir")
+ def __getattribute__(self, item):
+ builtin = object.__getattribute__(self, "builtin_way")
+ if builtin is not None and hasattr(builtin, item):
+ element = getattr(builtin, item)
+ if not callable(element):
+ return element
+ return object.__getattribute__(self, item)
diff --git a/src/virtualenv/interpreters/create/via_global_ref/python2.py b/src/virtualenv/interpreters/create/via_global_ref/python2.py
index 4ff6e84..9c7222a 100644
--- a/src/virtualenv/interpreters/create/via_global_ref/python2.py
+++ b/src/virtualenv/interpreters/create/via_global_ref/python2.py
@@ -27,16 +27,16 @@ class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports):
self.add_module(module)
# 2. install a patched site-package, the default Python 2 site.py is not smart enough to understand pyvenv.cfg,
# so we inject a small shim that can do this
- site_py = self.lib_dir / "site.py"
- relative_site_packages = [
- os.path.relpath(six.ensure_text(str(s)), six.ensure_text(str(site_py))) for s in self.site_packages
- ]
+ site_py = self.stdlib / "site.py"
custom_site = get_custom_site()
if IS_ZIPAPP:
custom_site_text = read_from_zipapp(custom_site)
else:
custom_site_text = custom_site.read_text()
- site_py.write_text(custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", json.dumps(relative_site_packages)))
+ expected = json.dumps(
+ [os.path.relpath(six.ensure_text(str(i)), six.ensure_text(str(site_py))) for i in self.libs]
+ )
+ site_py.write_text(custom_site_text.replace("___EXPECTED_SITE_PACKAGES___", expected))
@abc.abstractmethod
def modules(self):
@@ -48,10 +48,10 @@ class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports):
def add_module(self, req):
for ext in ["py", "pyc"]:
file_path = "{}.{}".format(req, ext)
- self.copier(self.system_stdlib / file_path, self.lib_dir / file_path)
+ self.copier(self.system_stdlib / file_path, self.stdlib / file_path)
def add_folder(self, folder):
- self.copier(self.system_stdlib / folder, self.lib_dir / folder)
+ self.copier(self.system_stdlib / folder, self.stdlib / folder)
def get_custom_site():
diff --git a/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py b/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py
index 7e259f3..5844a92 100644
--- a/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py
+++ b/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py
@@ -9,7 +9,7 @@ from six import add_metaclass
from virtualenv.info import is_fs_case_sensitive
from virtualenv.interpreters.create.builtin_way import VirtualenvBuiltin
-from virtualenv.util.path import Path, copy, ensure_dir, make_exe, symlink
+from virtualenv.util.path import copy, ensure_dir, make_exe, symlink
@add_metaclass(ABCMeta)
@@ -42,9 +42,7 @@ class ViaGlobalRefVirtualenvBuiltin(VirtualenvBuiltin):
self.pyenv_cfg["base-executable"] = self.interpreter.system_executable
def ensure_directories(self):
- dirs = {self.dest_dir, self.bin_dir, self.lib_dir}
- dirs.update(self.site_packages)
- return dirs
+ return {self.dest_dir, self.bin_dir, self.script_dir, self.stdlib} | set(self.libs)
def setup_python(self):
aliases = method = self.add_exe_method()
@@ -79,19 +77,3 @@ class ViaGlobalRefVirtualenvBuiltin(VirtualenvBuiltin):
def symlink_exe(src, dest):
symlink(src, dest)
make_exe(dest)
-
- @property
- def lib_base(self):
- raise NotImplementedError
-
- @property
- def system_stdlib(self):
- return Path(self.interpreter.system_prefix) / self.lib_base
-
- @property
- def lib_dir(self):
- return self.dest_dir / self.lib_base
-
- @abc.abstractmethod
- def lib_name(self):
- raise NotImplementedError
diff --git a/src/virtualenv/interpreters/discovery/py_info.py b/src/virtualenv/interpreters/discovery/py_info.py
index 9feecb4..0aebece 100644
--- a/src/virtualenv/interpreters/discovery/py_info.py
+++ b/src/virtualenv/interpreters/discovery/py_info.py
@@ -10,8 +10,12 @@ import logging
import os
import pipes
import platform
+import re
import sys
+import sysconfig
from collections import OrderedDict, namedtuple
+from distutils.command.install import SCHEME_KEYS
+from distutils.dist import Distribution
VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])
@@ -21,36 +25,41 @@ def _get_path_extensions():
EXTENSIONS = _get_path_extensions()
+_CONF_VAR_RE = re.compile(r"\{\w+\}")
class PythonInfo(object):
"""Contains information for a Python interpreter"""
def __init__(self):
+ def u(v):
+ return v.decode("utf-8") if isinstance(v, bytes) else v
+
# qualifies the python
- self.platform = sys.platform
- self.implementation = platform.python_implementation()
- self.pypy_version_info = tuple(sys.pypy_version_info) if self.implementation == "PyPy" else None
+ self.platform = u(sys.platform)
+ self.implementation = u(platform.python_implementation())
+ if self.implementation == "PyPy":
+ self.pypy_version_info = tuple(u(i) for i in sys.pypy_version_info)
# this is a tuple in earlier, struct later, unify to our own named tuple
- self.version_info = VersionInfo(*list(sys.version_info))
+ self.version_info = VersionInfo(*list(u(i) for i in sys.version_info))
self.architecture = 64 if sys.maxsize > 2 ** 32 else 32
- self.executable = sys.executable # executable we were called with
- self.original_executable = self.executable
- self.base_executable = getattr(sys, "_base_executable", None) # some platforms may set this
+ self.executable = u(sys.executable) # executable we were called with
+ self.original_executable = u(self.executable)
+ self.base_executable = u(getattr(sys, "_base_executable", None)) # some platforms may set this
- self.version = sys.version
- self.os = os.name
+ self.version = u(sys.version)
+ self.os = u(os.name)
# information about the prefix - determines python home
- self.prefix = getattr(sys, "prefix", None) # prefix we think
- self.base_prefix = getattr(sys, "base_prefix", None) # venv
- self.real_prefix = getattr(sys, "real_prefix", None) # old virtualenv
+ self.prefix = u(getattr(sys, "prefix", None)) # prefix we think
+ self.base_prefix = u(getattr(sys, "base_prefix", None)) # venv
+ self.real_prefix = u(getattr(sys, "real_prefix", None)) # old virtualenv
# information about the exec prefix - dynamic stdlib modules
- self.base_exec_prefix = getattr(sys, "base_exec_prefix", None)
- self.exec_prefix = getattr(sys, "exec_prefix", None)
+ self.base_exec_prefix = u(getattr(sys, "base_exec_prefix", None))
+ self.exec_prefix = u(getattr(sys, "exec_prefix", None))
try:
__import__("venv")
@@ -58,9 +67,28 @@ class PythonInfo(object):
except ImportError:
has = False
self.has_venv = has
- self.path = sys.path
- self.file_system_encoding = sys.getfilesystemencoding()
- self.stdout_encoding = getattr(sys.stdout, "encoding", None)
+ self.path = [u(i) for i in sys.path]
+ self.file_system_encoding = u(sys.getfilesystemencoding())
+ 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()}
+ config_var_keys = set()
+ for element in self.sysconfig_paths.values():
+ for k in _CONF_VAR_RE.findall(element):
+ config_var_keys.add(u(k[1:-1]))
+ self.sysconfig_config_vars = {u(i): u(sysconfig.get_config_var(i)) for i in config_var_keys}
+
+ self.distutils_install = {u(k): u(v) for k, v in self._distutils_install().items()}
+
+ def _distutils_install(self):
+ # follow https://github.com/pypa/pip/blob/master/src/pip/_internal/locations.py#L95
+ d = Distribution({"script_args": "--no-user-cfg"})
+ d.parse_config_files()
+ i = d.get_command_obj("install", create=True)
+ i.prefix = "a"
+ i.finalize_options()
+ result = {key: (getattr(i, "install_{}".format(key))[1:]).lstrip(os.sep) for key in SCHEME_KEYS}
+ return result
@property
def version_str(self):
@@ -132,8 +160,7 @@ class PythonInfo(object):
data = json.loads(payload)
data["version_info"] = VersionInfo(**data["version_info"]) # restore this to a named tuple structure
result = cls()
- for var in vars(result):
- setattr(result, var, data[var])
+ result.__dict__ = {k: v for k, v in data.items()}
return result
@property
@@ -249,7 +276,7 @@ class PythonInfo(object):
cmd = cls._get_exe_cmd(exe)
# noinspection DuplicatedCode
# this is duplicated here because this file is executed on its own, so cannot be refactored otherwise
- logging.debug("get interpreter info via cmd: %s", Cmd(cmd))
+ logging.debug(u"get interpreter info via cmd: %s", Cmd(cmd))
try:
process = Popen(
cmd, universal_newlines=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE
@@ -307,6 +334,16 @@ class PythonInfo(object):
return False
return True
+ def sysconfig_path(self, key, config_var=None, sep=os.sep):
+ pattern = self.sysconfig_paths[key]
+ if config_var is None:
+ config_var = self.sysconfig_config_vars
+ else:
+ base = {k: v for k, v in self.sysconfig_config_vars.items()}
+ base.update(config_var)
+ config_var = base
+ return pattern.format(**config_var).replace(u"/", sep)
+
class Cmd(object):
def __init__(self, cmd, env=None):
@@ -314,9 +351,12 @@ class Cmd(object):
self.env = env
def __repr__(self):
- cmd_repr = " ".join(pipes.quote(c) for c in self.cmd)
+ def e(v):
+ return v.decode("utf-8") if isinstance(v, bytes) else v
+
+ cmd_repr = e(" ").join(pipes.quote(e(c)) for c in self.cmd)
if self.env is not None:
- cmd_repr += " env of {!r}".format(self.env)
+ cmd_repr += e(" env of {!r}").format(self.env)
return cmd_repr
diff --git a/src/virtualenv/pyenv_cfg.py b/src/virtualenv/pyenv_cfg.py
index 05daa5a..218c759 100644
--- a/src/virtualenv/pyenv_cfg.py
+++ b/src/virtualenv/pyenv_cfg.py
@@ -54,3 +54,6 @@ class PyEnvCfg(object):
def update(self, other):
self.content.update(other)
return self
+
+ def __repr__(self):
+ return "{}(path={})".format(self.__class__.__name__, self.path)
diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py
index ba65e9a..7082cf6 100644
--- a/src/virtualenv/seed/via_app_data/pip_install/base.py
+++ b/src/virtualenv/seed/via_app_data/pip_install/base.py
@@ -34,9 +34,8 @@ class PipInstall(object):
def install(self):
self._extracted = True
# sync image
- site_package = self._creator.site_packages[0]
for filename in self._image_dir.iterdir():
- into = site_package / filename.name
+ into = self._creator.purelib / filename.name
logging.debug("%s %s from %s", self.__class__.__name__, into, filename)
if into.exists():
if into.is_dir() and not into.is_symlink():
@@ -46,9 +45,9 @@ class PipInstall(object):
self._sync(filename, into)
# generate console executables
consoles = set()
- bin_dir = self._creator.bin_dir
+ script_dir = self._creator.script_dir
for name, module in self._console_scripts.items():
- consoles.update(self._create_console_entry_point(name, module, bin_dir))
+ consoles.update(self._create_console_entry_point(name, module, script_dir))
logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
def build_image(self):
@@ -83,7 +82,7 @@ class PipInstall(object):
try:
to_folder = Path(folder)
rel = os.path.relpath(
- six.ensure_text(str(self._creator.bin_dir)), six.ensure_text(str(self._creator.site_packages[0]))
+ six.ensure_text(str(self._creator.script_dir)), six.ensure_text(str(self._creator.purelib))
)
for name, module in self._console_scripts.items():
new_files.update(
diff --git a/src/virtualenv/util/path/_permission.py b/src/virtualenv/util/path/_permission.py
index 1356b6d..85f93b8 100644
--- a/src/virtualenv/util/path/_permission.py
+++ b/src/virtualenv/util/path/_permission.py
@@ -13,7 +13,7 @@ def make_exe(filename):
mode |= level
filename.chmod(mode)
break
- except PermissionError:
+ except OSError:
continue
diff --git a/src/virtualenv/util/path/_sync.py b/src/virtualenv/util/path/_sync.py
index 860aee1..4c5aa86 100644
--- a/src/virtualenv/util/path/_sync.py
+++ b/src/virtualenv/util/path/_sync.py
@@ -23,6 +23,10 @@ def symlink_or_copy(do_copy, src, dst, relative_symlinks_ok=False):
"""
Try symlinking a target, and if that fails, fall back to copying.
"""
+ if not src.exists():
+ raise RuntimeError("source {} does not exists".format(src))
+ if src == dst:
+ raise RuntimeError("source {} is same as destination ".format(src))
def norm(val):
if IS_PYPY and six.PY3:
diff --git a/src/virtualenv/util/subprocess/_win_subprocess.py b/src/virtualenv/util/subprocess/_win_subprocess.py
index e8fdaf0..f30344d 100644
--- a/src/virtualenv/util/subprocess/_win_subprocess.py
+++ b/src/virtualenv/util/subprocess/_win_subprocess.py
@@ -5,7 +5,6 @@
import ctypes
import os
import subprocess
-import sys
from ctypes import Structure, WinError, byref, c_char_p, c_void_p, c_wchar, c_wchar_p, sizeof, windll
from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPVOID, LPWSTR, WORD
diff --git a/tests/conftest.py b/tests/conftest.py
index 606c9c8..678eb55 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -218,7 +218,7 @@ class EnableCoverage(object):
def __enter__(self, creator):
assert not self.cov_pth.exists()
- site_packages = creator.site_packages[0]
+ site_packages = creator.purelib
p_th = site_packages / self._SUBPROCESS_TRIGGER_PTH_NAME
if not str(p_th).startswith(str(self._COV_SITE_PACKAGES)):
diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py
index 79ff5d9..06980a5 100644
--- a/tests/unit/activation/conftest.py
+++ b/tests/unit/activation/conftest.py
@@ -206,7 +206,7 @@ def raise_on_non_source_class():
def activation_python(tmp_path_factory, special_char_name):
dest = os.path.join(six.ensure_text(str(tmp_path_factory.mktemp("activation-tester-env"))), special_char_name)
session = run_via_cli(["--seed", "none", dest, "--prompt", special_char_name, "--creator", "builtin"])
- pydoc_test = session.creator.site_packages[0] / "pydoc_test.py"
+ pydoc_test = session.creator.purelib / "pydoc_test.py"
with open(six.ensure_text(str(pydoc_test)), "wb") as file_handler:
file_handler.write(b'"""This is pydoc_test.py"""')
yield session
diff --git a/tests/unit/activation/test_python_activator.py b/tests/unit/activation/test_python_activator.py
index 3db0b93..f696bf8 100644
--- a/tests/unit/activation/test_python_activator.py
+++ b/tests/unit/activation/test_python_activator.py
@@ -1,8 +1,8 @@
from __future__ import absolute_import, unicode_literals
-import inspect
import os
import sys
+from textwrap import dedent
import pytest
import six
@@ -36,17 +36,9 @@ def test_python(raise_on_non_source_class, activation_tester):
env[str("PATH")] = os.pathsep.join([str(tmp_path), str(tmp_path / "other")])
return env
- def _get_test_lines(self, activate_script):
- raw = inspect.getsource(self.activate_this_test)
- return [
- i[12:]
- for i in raw.replace('"__FILENAME__"', repr(six.ensure_text(str(activate_script)))).splitlines()[2:]
- ]
-
- # noinspection PyUnresolvedReferences
@staticmethod
- def activate_this_test():
- """Used as template for the test - unicode literals don't apply"""
+ def _get_test_lines(activate_script):
+ raw = """
import os
import sys
@@ -60,10 +52,10 @@ def test_python(raise_on_non_source_class, activation_tester):
print_path(os.environ.get("VIRTUAL_ENV"))
print_path(os.environ.get("PATH"))
print_path(os.pathsep.join(sys.path))
- file_at = "__FILENAME__"
+ file_at = {}
with open(file_at, "rb") as file_handler:
content = file_handler.read()
- exec(content, {"__file__": file_at})
+ exec(content, {{"__file__": file_at}})
print_path(os.environ.get("VIRTUAL_ENV"))
print_path(os.environ.get("PATH"))
print_path(os.pathsep.join(sys.path))
@@ -71,6 +63,11 @@ def test_python(raise_on_non_source_class, activation_tester):
import pydoc_test
print_path(inspect.getsourcefile(pydoc_test))
+ """.format(
+ repr(six.ensure_text(str(activate_script)))
+ )
+ result = dedent(raw).splitlines()
+ return result
def assert_output(self, out, raw, tmp_path):
assert out[0] == "None" # start with VIRTUAL_ENV None
@@ -87,10 +84,13 @@ def test_python(raise_on_non_source_class, activation_tester):
# sys path contains the site package at its start
new_sys_path = out[5].split(os.path.pathsep)
- assert ([six.ensure_text(str(i)) for i in self._creator.site_packages] + prev_sys_path) == new_sys_path
+
+ new_lib_paths = {six.ensure_text(str(i)) for i in self._creator.libs}
+ assert prev_sys_path == new_sys_path[len(new_lib_paths) :]
+ assert new_lib_paths == set(new_sys_path[: len(new_lib_paths)])
# manage to import from activate site package
- assert self.norm_path(out[6]) == self.norm_path(self._creator.site_packages[0] / "pydoc_test.py")
+ assert self.norm_path(out[6]) == self.norm_path(self._creator.purelib / "pydoc_test.py")
def non_source_activate(self, activate_script):
return self._invoke_script + [
diff --git a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py
index 618d281..2ae45c0 100644
--- a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py
+++ b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py
@@ -38,17 +38,15 @@ def test_base_bootstrap_link_via_app_data(tmp_path, coverage_env, mocker):
assert result
# uninstalling pip/setuptools now should leave us with a clean env
- site_package = result.creator.site_packages[0]
+ site_package = result.creator.purelib
pip = site_package / "pip"
setuptools = site_package / "setuptools"
files_post_first_create = list(site_package.iterdir())
assert pip in files_post_first_create
assert setuptools in files_post_first_create
-
- env_exe = result.creator.exe
for pip_exe in [
- env_exe.with_name("pip{}{}".format(suffix, env_exe.suffix))
+ result.creator.script_dir / "pip{}{}".format(suffix, result.creator.exe.suffix)
for suffix in (
"",
"{}".format(CURRENT.version_info.major),
@@ -61,7 +59,7 @@ def test_base_bootstrap_link_via_app_data(tmp_path, coverage_env, mocker):
assert not process.returncode
remove_cmd = [
- str(env_exe),
+ str(result.creator.exe),
"-m",
"pip",
"--verbose",
diff --git a/tests/unit/interpreters/boostrap/test_pip_invoke.py b/tests/unit/interpreters/boostrap/test_pip_invoke.py
index 8904c13..645f1fc 100644
--- a/tests/unit/interpreters/boostrap/test_pip_invoke.py
+++ b/tests/unit/interpreters/boostrap/test_pip_invoke.py
@@ -27,7 +27,7 @@ def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env):
assert result
# uninstalling pip/setuptools now should leave us with a clean env
- site_package = result.creator.site_packages[0]
+ site_package = result.creator.purelib
pip = site_package / "pip"
setuptools = site_package / "setuptools"
wheel = site_package / "wheel"
diff --git a/tests/unit/interpreters/create/test_creator.py b/tests/unit/interpreters/create/test_creator.py
index a3d0dec..02bfdb2 100644
--- a/tests/unit/interpreters/create/test_creator.py
+++ b/tests/unit/interpreters/create/test_creator.py
@@ -10,7 +10,7 @@ import pytest
import six
from virtualenv.__main__ import run
-from virtualenv.info import IS_PYPY
+from virtualenv.info import IS_PYPY, IS_WIN
from virtualenv.interpreters.create.creator import DEBUG_SCRIPT, get_env_debug_info
from virtualenv.interpreters.discovery.builtin import get_interpreter
from virtualenv.interpreters.discovery.py_info import CURRENT, PythonInfo
@@ -77,11 +77,10 @@ def system():
return get_env_debug_info(Path(CURRENT.system_executable), DEBUG_SCRIPT)
-@pytest.mark.parametrize("global_access", [False, True], ids=["no_global", "ok_global"])
-@pytest.mark.parametrize(
- "use_venv", [False, True] if six.PY3 else [False], ids=["no_venv", "venv"] if six.PY3 else ["no_venv"]
-)
-def test_create_no_seed(python, use_venv, global_access, system, coverage_env, special_name_dir):
+@pytest.mark.parametrize("isolated", [True, False], ids=["isolated", "with_global_site"])
+@pytest.mark.parametrize("method", (["copies"] + ([] if IS_WIN else ["symlinks"])))
+@pytest.mark.parametrize("creator", (["builtin"] + (["venv"] if six.PY3 else [])))
+def test_create_no_seed(python, creator, isolated, system, coverage_env, special_name_dir, method):
dest = special_name_dir
cmd = [
"-v",
@@ -93,9 +92,10 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s
"--activators",
"",
"--creator",
- "venv" if use_venv else "builtin",
+ creator,
+ "--{}".format(method),
]
- if global_access:
+ if not isolated:
cmd.append("--system-site-packages")
result = run_via_cli(cmd)
coverage_env()
@@ -103,9 +103,8 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s
# pypy cleans up file descriptors periodically so our (many) subprocess calls impact file descriptor limits
# force a cleanup of these on system where the limit is low-ish (e.g. MacOS 256)
gc.collect()
- for site_package in result.creator.site_packages:
- content = list(site_package.iterdir())
- assert not content, "\n".join(str(i) for i in content)
+ content = list(result.creator.purelib.iterdir())
+ assert not content, "\n".join(six.ensure_text(str(i)) for i in content)
assert result.creator.env_name == six.ensure_text(dest.name)
debug = result.creator.debug
sys_path = cleanup_sys_path(debug["sys"]["path"])
@@ -128,7 +127,9 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s
# ensure the global site package is added or not, depending on flag
last_from_system_path = next(i for i in reversed(system_sys_path) if str(i).startswith(system["sys"]["prefix"]))
- if global_access:
+ if isolated:
+ assert last_from_system_path not in sys_path
+ else:
common = []
for left, right in zip(reversed(system_sys_path), reversed(sys_path)):
if left == right:
@@ -140,8 +141,6 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s
return [six.ensure_text(str(i)) for i in iterable]
assert common, "\n".join(difflib.unified_diff(list_to_str(sys_path), list_to_str(system_sys_path)))
- else:
- assert last_from_system_path not in sys_path
@pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv")
@@ -174,7 +173,7 @@ def test_debug_bad_virtualenv(tmp_path):
cmd = [str(tmp_path), "--without-pip"]
result = run_via_cli(cmd)
# if the site.py is removed/altered the debug should fail as no one is around to fix the paths
- site_py = result.creator.lib_dir / "site.py"
+ site_py = result.creator.stdlib / "site.py"
site_py.unlink()
# insert something that writes something on the stdout
site_py.write_text('import sys; sys.stdout.write(repr("std-out")); sys.stderr.write("std-err"); raise ValueError')