summaryrefslogtreecommitdiff
path: root/src/virtualenv
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2020-01-02 16:32:54 +0000
committerBernat Gabor <bgabor8@bloomberg.net>2020-01-10 15:38:36 +0000
commitff6dc73d447a3c6276af64df2eb91e2709e450a3 (patch)
treecf2c4be51c557ce2157cc32279cbd53464df3bf5 /src/virtualenv
parent1cb5216252dbb144a3ee3976f9ec92def3dfc6db (diff)
downloadvirtualenv-ff6dc73d447a3c6276af64df2eb91e2709e450a3.tar.gz
unicode support (#1477)
* creator unicode support Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * activator support Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * fix * add space * python3.4 support * Windows fixes * some fixes * fix powershell requires utf-16 * try to fix python2 windows Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * use utf-8 for activation scripts Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * fix * more fix Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * fix Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * windows path py2.7 * fixes for Python 2 and unicode on Windows * do not single out mbcs, but the file system encoder * do not install pathlib python 2 windows Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net> * fix encoding on py35 Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
Diffstat (limited to 'src/virtualenv')
-rw-r--r--src/virtualenv/activation/bash/__init__.py2
-rw-r--r--src/virtualenv/activation/bash/activate.sh2
-rw-r--r--src/virtualenv/activation/batch/__init__.py2
-rw-r--r--src/virtualenv/activation/cshell/__init__.py2
-rw-r--r--src/virtualenv/activation/cshell/activate.csh6
-rw-r--r--src/virtualenv/activation/fish/__init__.py2
-rw-r--r--src/virtualenv/activation/fish/activate.fish22
-rw-r--r--src/virtualenv/activation/powershell/__init__.py2
-rw-r--r--src/virtualenv/activation/python/__init__.py2
-rw-r--r--src/virtualenv/activation/python/activate_this.py16
-rw-r--r--src/virtualenv/activation/via_template.py8
-rw-r--r--src/virtualenv/activation/xonosh/__init__.py2
-rw-r--r--src/virtualenv/config/ini.py3
-rw-r--r--src/virtualenv/info.py3
-rw-r--r--src/virtualenv/interpreters/create/cpython/common.py3
-rw-r--r--src/virtualenv/interpreters/create/cpython/cpython2.py3
-rw-r--r--src/virtualenv/interpreters/create/cpython/cpython3.py3
-rw-r--r--src/virtualenv/interpreters/create/creator.py82
-rw-r--r--src/virtualenv/interpreters/create/debug.py30
-rw-r--r--src/virtualenv/interpreters/create/venv.py2
-rw-r--r--src/virtualenv/interpreters/discovery/builtin.py37
-rw-r--r--src/virtualenv/interpreters/discovery/py_info.py7
-rw-r--r--src/virtualenv/pyenv_cfg.py12
-rw-r--r--src/virtualenv/seed/embed/link_app_data.py6
-rw-r--r--src/virtualenv/seed/embed/pip_invoke.py5
-rw-r--r--src/virtualenv/seed/embed/wheels/acquire.py4
-rw-r--r--src/virtualenv/util/__init__.py (renamed from src/virtualenv/util.py)28
-rw-r--r--src/virtualenv/util/path.py137
-rw-r--r--src/virtualenv/util/subprocess/__init__.py15
-rw-r--r--src/virtualenv/util/subprocess/win_subprocess.py153
30 files changed, 481 insertions, 120 deletions
diff --git a/src/virtualenv/activation/bash/__init__.py b/src/virtualenv/activation/bash/__init__.py
index fd53741..7c85564 100644
--- a/src/virtualenv/activation/bash/__init__.py
+++ b/src/virtualenv/activation/bash/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/bash/activate.sh b/src/virtualenv/activation/bash/activate.sh
index d9b8781..19bf552 100644
--- a/src/virtualenv/activation/bash/activate.sh
+++ b/src/virtualenv/activation/bash/activate.sh
@@ -46,7 +46,7 @@ deactivate () {
# unset irrelevant variables
deactivate nondestructive
-VIRTUAL_ENV="__VIRTUAL_ENV__"
+VIRTUAL_ENV='__VIRTUAL_ENV__'
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
diff --git a/src/virtualenv/activation/batch/__init__.py b/src/virtualenv/activation/batch/__init__.py
index 4e4b839..2c3da44 100644
--- a/src/virtualenv/activation/batch/__init__.py
+++ b/src/virtualenv/activation/batch/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/cshell/__init__.py b/src/virtualenv/activation/cshell/__init__.py
index e818ede..6a96d56 100644
--- a/src/virtualenv/activation/cshell/__init__.py
+++ b/src/virtualenv/activation/cshell/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/cshell/activate.csh b/src/virtualenv/activation/cshell/activate.csh
index c4a6d58..72b2cf8 100644
--- a/src/virtualenv/activation/cshell/activate.csh
+++ b/src/virtualenv/activation/cshell/activate.csh
@@ -10,15 +10,15 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
# Unset irrelevant variables.
deactivate nondestructive
-setenv VIRTUAL_ENV "__VIRTUAL_ENV__"
+setenv VIRTUAL_ENV '__VIRTUAL_ENV__'
set _OLD_VIRTUAL_PATH="$PATH:q"
setenv PATH "$VIRTUAL_ENV:q/__BIN_NAME__:$PATH:q"
-if ("__VIRTUAL_PROMPT__" != "") then
- set env_name = "__VIRTUAL_PROMPT__"
+if ('__VIRTUAL_PROMPT__' != "") then
+ set env_name = '__VIRTUAL_PROMPT__'
else
set env_name = '('"$VIRTUAL_ENV:t:q"') '
endif
diff --git a/src/virtualenv/activation/fish/__init__.py b/src/virtualenv/activation/fish/__init__.py
index 0e54406..0dfe63c 100644
--- a/src/virtualenv/activation/fish/__init__.py
+++ b/src/virtualenv/activation/fish/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/fish/activate.fish b/src/virtualenv/activation/fish/activate.fish
index 4e29768..770fb0d 100644
--- a/src/virtualenv/activation/fish/activate.fish
+++ b/src/virtualenv/activation/fish/activate.fish
@@ -18,16 +18,16 @@ function deactivate -d 'Exit virtualenv mode and return to the normal environmen
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
- if test (echo $FISH_VERSION | tr "." "\n")[1] -lt 3
- set -gx PATH (_fishify_path $_OLD_VIRTUAL_PATH)
+ if test (echo $FISH_VERSION | head -c 1) -lt 3
+ set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
else
- set -gx PATH $_OLD_VIRTUAL_PATH
+ set -gx PATH "$_OLD_VIRTUAL_PATH"
end
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
- set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+ set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
set -e _OLD_VIRTUAL_PYTHONHOME
end
@@ -57,15 +57,15 @@ end
# Unset irrelevant variables.
deactivate nondestructive
-set -gx VIRTUAL_ENV "__VIRTUAL_ENV__"
+set -gx VIRTUAL_ENV '__VIRTUAL_ENV__'
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
-if test (echo $FISH_VERSION | tr "." "\n")[1] -lt 3
+if test (echo $FISH_VERSION | head -c 1) -lt 3
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
else
- set -gx _OLD_VIRTUAL_PATH $PATH
+ set -gx _OLD_VIRTUAL_PATH "$PATH"
end
-set -gx PATH "$VIRTUAL_ENV/__BIN_NAME__" $PATH
+set -gx PATH "$VIRTUAL_ENV"'/__BIN_NAME__' $PATH
# Unset `$PYTHONHOME` if set.
if set -q PYTHONHOME
@@ -87,10 +87,10 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# Prompt override provided?
# If not, just prepend the environment name.
- if test -n "__VIRTUAL_PROMPT__"
- printf '%s%s' "__VIRTUAL_PROMPT__" (set_color normal)
+ if test -n '__VIRTUAL_PROMPT__'
+ printf '%s%s' '__VIRTUAL_PROMPT__' (set_color normal)
else
- printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV")
+ printf '%s(%s) ' (set_color normal) (basename '$VIRTUAL_ENV')
end
# Restore the original $status
diff --git a/src/virtualenv/activation/powershell/__init__.py b/src/virtualenv/activation/powershell/__init__.py
index b5b0a75..a3c6f9e 100644
--- a/src/virtualenv/activation/powershell/__init__.py
+++ b/src/virtualenv/activation/powershell/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/python/__init__.py b/src/virtualenv/activation/python/__init__.py
index 1d73e99..36b2b2c 100644
--- a/src/virtualenv/activation/python/__init__.py
+++ b/src/virtualenv/activation/python/__init__.py
@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
import json
import os
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/activation/python/activate_this.py b/src/virtualenv/activation/python/activate_this.py
index fc8d449..3311fe8 100644
--- a/src/virtualenv/activation/python/activate_this.py
+++ b/src/virtualenv/activation/python/activate_this.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Activate virtualenv for current interpreter:
Use exec(open(this_file).read(), {'__file__': this_file}).
@@ -14,14 +15,21 @@ try:
except NameError:
raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))")
+
+def set_env(key, value, encoding):
+ if sys.version_info[0] == 2:
+ value = value.encode(encoding)
+ os.environ[key] = value
+
+
# prepend bin to PATH (this file is inside the bin directory)
bin_dir = os.path.dirname(os.path.abspath(__file__))
-os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))
+set_env("PATH", os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)), sys.getfilesystemencoding())
base = os.path.dirname(bin_dir)
# virtual env is right above bin directory
-os.environ["VIRTUAL_ENV"] = base
+set_env("VIRTUAL_ENV", base, sys.getfilesystemencoding())
# add the virtual environments site-packages to the host python import mechanism
prev = set(sys.path)
@@ -33,7 +41,11 @@ __SITE_PACKAGES__
'''
for site_package in json.loads(site_packages):
+ if sys.version_info[0] == 2:
+ site_package = site_package.encode('utf-8').decode(sys.getfilesystemencoding())
path = os.path.realpath(os.path.join(os.path.dirname(__file__), site_package))
+ if sys.version_info[0] == 2:
+ path = path.encode(sys.getfilesystemencoding())
site.addsitedir(path)
# fmt: on
diff --git a/src/virtualenv/activation/via_template.py b/src/virtualenv/activation/via_template.py
index 864454f..e7b3aad 100644
--- a/src/virtualenv/activation/via_template.py
+++ b/src/virtualenv/activation/via_template.py
@@ -24,9 +24,9 @@ class ViaTemplateActivator(Activator):
def replacements(self, creator, dest_folder):
return {
"__VIRTUAL_PROMPT__": "" if self.flag_prompt is None else self.flag_prompt,
- "__VIRTUAL_ENV__": str(creator.dest_dir),
- "__VIRTUAL_NAME__": str(creator.env_name),
- "__BIN_NAME__": str(creator.bin_name),
+ "__VIRTUAL_ENV__": six.ensure_text(str(creator.dest_dir)),
+ "__VIRTUAL_NAME__": creator.env_name,
+ "__BIN_NAME__": six.ensure_text(str(creator.bin_name)),
"__PATH_SEP__": os.pathsep,
}
@@ -35,4 +35,4 @@ class ViaTemplateActivator(Activator):
text = pkgutil.get_data(self.__module__, str(template)).decode("utf-8")
for start, end in replacements.items():
text = text.replace(start, end)
- (to_folder / template).write_text(text)
+ (to_folder / template).write_text(text, encoding="utf-8")
diff --git a/src/virtualenv/activation/xonosh/__init__.py b/src/virtualenv/activation/xonosh/__init__.py
index ceb5340..94546ff 100644
--- a/src/virtualenv/activation/xonosh/__init__.py
+++ b/src/virtualenv/activation/xonosh/__init__.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import, unicode_literals
-from pathlib2 import Path
+from virtualenv.util import Path
from ..via_template import ViaTemplateActivator
diff --git a/src/virtualenv/config/ini.py b/src/virtualenv/config/ini.py
index 9c86e48..4acb70f 100644
--- a/src/virtualenv/config/ini.py
+++ b/src/virtualenv/config/ini.py
@@ -3,9 +3,8 @@ from __future__ import absolute_import, unicode_literals
import logging
import os
-from pathlib2 import Path
-
from virtualenv.info import PY3, get_default_config_dir
+from virtualenv.util import Path
from .convert import convert
diff --git a/src/virtualenv/info.py b/src/virtualenv/info.py
index 149f852..294110e 100644
--- a/src/virtualenv/info.py
+++ b/src/virtualenv/info.py
@@ -3,7 +3,8 @@ from __future__ import absolute_import, unicode_literals
import sys
from appdirs import user_config_dir, user_data_dir
-from pathlib2 import Path
+
+from virtualenv.util import Path
IS_PYPY = hasattr(sys, "pypy_version_info")
PY3 = sys.version_info[0] == 3
diff --git a/src/virtualenv/interpreters/create/cpython/common.py b/src/virtualenv/interpreters/create/cpython/common.py
index d05d47b..89a91c6 100644
--- a/src/virtualenv/interpreters/create/cpython/common.py
+++ b/src/virtualenv/interpreters/create/cpython/common.py
@@ -4,10 +4,9 @@ import abc
from os import X_OK, access, chmod
import six
-from pathlib2 import Path
from virtualenv.interpreters.create.via_global_ref import ViaGlobalRef
-from virtualenv.util import copy, ensure_dir, symlink
+from virtualenv.util import Path, copy, ensure_dir, symlink
@six.add_metaclass(abc.ABCMeta)
diff --git a/src/virtualenv/interpreters/create/cpython/cpython2.py b/src/virtualenv/interpreters/create/cpython/cpython2.py
index fbaee88..7c79158 100644
--- a/src/virtualenv/interpreters/create/cpython/cpython2.py
+++ b/src/virtualenv/interpreters/create/cpython/cpython2.py
@@ -3,9 +3,8 @@ from __future__ import absolute_import, unicode_literals
import abc
import six
-from pathlib2 import Path
-from virtualenv.util import copy
+from virtualenv.util import Path, copy
from .common import CPython, CPythonPosix, CPythonWindows
diff --git a/src/virtualenv/interpreters/create/cpython/cpython3.py b/src/virtualenv/interpreters/create/cpython/cpython3.py
index 3249306..b14ce0a 100644
--- a/src/virtualenv/interpreters/create/cpython/cpython3.py
+++ b/src/virtualenv/interpreters/create/cpython/cpython3.py
@@ -3,9 +3,8 @@ from __future__ import absolute_import, unicode_literals
import abc
import six
-from pathlib2 import Path
-from virtualenv.util import copy
+from virtualenv.util import Path, copy
from .common import CPython, CPythonPosix, CPythonWindows
diff --git a/src/virtualenv/interpreters/create/creator.py b/src/virtualenv/interpreters/create/creator.py
index 872d8d1..b917a37 100644
--- a/src/virtualenv/interpreters/create/creator.py
+++ b/src/virtualenv/interpreters/create/creator.py
@@ -4,15 +4,16 @@ import json
import logging
import os
import shutil
+import sys
from abc import ABCMeta, abstractmethod
from argparse import ArgumentTypeError
-from pathlib2 import Path
+import six
from six import add_metaclass
from virtualenv.info import IS_WIN
from virtualenv.pyenv_cfg import PyEnvCfg
-from virtualenv.util import run_cmd
+from virtualenv.util import Path, run_cmd
from virtualenv.version import __version__
HERE = Path(__file__).absolute().parent
@@ -47,28 +48,13 @@ class Creator(object):
help="Give the virtual environment access to the system site-packages dir.",
)
- def validate_dest_dir(value):
- """No path separator in the path and must be write-able"""
- if os.pathsep in value:
- raise ArgumentTypeError(
- "destination {!r} must not contain the path separator ({}) as this would break "
- "the activation scripts".format(value, os.pathsep)
- )
- value = Path(value)
- if value.exists() and value.is_file():
- raise ArgumentTypeError("the destination {} already exists and is a file".format(value))
- value = dest = value.resolve()
- while dest:
- if dest.exists():
- if os.access(str(dest), os.W_OK):
- break
- else:
- non_write_able(dest, value)
- base, _ = dest.parent, dest.name
- if base == dest:
- non_write_able(dest, value) # pragma: no cover
- dest = base
- return str(value)
+ parser.add_argument(
+ "dest_dir", help="directory to create virtualenv at", type=cls.validate_dest_dir, default="env", nargs="?",
+ )
+
+ @classmethod
+ def validate_dest_dir(cls, raw_value):
+ """No path separator in the path, valid chars and must be write-able"""
def non_write_able(dest, value):
common = Path(*os.path.commonprefix([value.parts, dest.parts]))
@@ -76,9 +62,45 @@ class Creator(object):
"the destination {} is not write-able at {}".format(dest.relative_to(common), common)
)
- parser.add_argument(
- "dest_dir", help="directory to create virtualenv at", type=validate_dest_dir, default="env", nargs="?",
- )
+ # the file system must be able to encode
+ # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/
+ encoding = sys.getfilesystemencoding()
+ path_converted = raw_value.encode(encoding, errors="ignore").decode(encoding)
+ if path_converted != raw_value:
+ refused = set(raw_value) - {
+ c
+ for c, i in ((char, char.encode(encoding)) for char in raw_value)
+ if c == "?" or i != six.ensure_str("?")
+ }
+ raise ArgumentTypeError(
+ "the file system codec ({}) does not support characters {!r}".format(encoding, refused)
+ )
+ if os.pathsep in raw_value:
+ raise ArgumentTypeError(
+ "destination {!r} must not contain the path separator ({}) as this would break "
+ "the activation scripts".format(raw_value, os.pathsep)
+ )
+
+ value = Path(raw_value)
+ if value.exists() and value.is_file():
+ raise ArgumentTypeError("the destination {} already exists and is a file".format(value))
+ if (3, 3) <= sys.version_info <= (3, 6):
+ # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation
+ dest = Path(os.path.realpath(raw_value))
+ else:
+ dest = value.resolve()
+ value = dest
+ while dest:
+ if dest.exists():
+ if os.access(six.ensure_text(str(dest)), os.W_OK):
+ break
+ else:
+ non_write_able(dest, value)
+ base, _ = dest.parent, dest.name
+ if base == dest:
+ non_write_able(dest, value) # pragma: no cover
+ dest = base
+ return str(value)
def run(self):
if self.dest_dir.exists() and self.clear:
@@ -104,7 +126,7 @@ class Creator(object):
@property
def env_name(self):
- return self.dest_dir.parts[-1]
+ return six.ensure_text(self.dest_dir.parts[-1])
@property
def bin_name(self):
@@ -138,8 +160,8 @@ class Creator(object):
def get_env_debug_info(env_exe, debug_script):
- cmd = [str(env_exe), str(debug_script)]
- logging.debug(" ".join(cmd))
+ cmd = [six.ensure_text(str(env_exe)), six.ensure_text(str(debug_script))]
+ logging.debug(" ".join(six.ensure_text(i) for i in cmd))
env = os.environ.copy()
env.pop("PYTHONPATH", None)
code, out, err = run_cmd(cmd)
diff --git a/src/virtualenv/interpreters/create/debug.py b/src/virtualenv/interpreters/create/debug.py
index cb4da8e..37f1a45 100644
--- a/src/virtualenv/interpreters/create/debug.py
+++ b/src/virtualenv/interpreters/create/debug.py
@@ -2,6 +2,20 @@
import sys # built-in
+def encode_path(value):
+ if value is None:
+ return None
+ if isinstance(value, bytes):
+ return value.decode(sys.getfilesystemencoding())
+ if isinstance(value, type):
+ return repr(value)
+ return value
+
+
+def encode_list_path(value):
+ return [encode_path(i) for i in value]
+
+
def run():
"""print debug data about the virtual environment"""
try:
@@ -11,7 +25,7 @@ def run():
# noinspection PyPep8Naming
OrderedDict = dict # pragma: no cover
result = OrderedDict([("sys", OrderedDict())])
- for key in (
+ path_keys = (
"executable",
"_base_executable",
"prefix",
@@ -21,13 +35,15 @@ def run():
"base_exec_prefix",
"path",
"meta_path",
- "version",
- ):
+ )
+ for key in path_keys:
value = getattr(sys, key, None)
- if key == "meta_path" and value is not None:
- value = [repr(i) for i in value]
+ if isinstance(value, list):
+ value = encode_list_path(value)
+ else:
+ value = encode_path(value)
result["sys"][key] = value
-
+ result["version"] = sys.version
import os # landmark
result["os"] = os.__file__
@@ -45,7 +61,7 @@ def run():
result["json"] = repr(json)
print(json.dumps(result, indent=2))
- except ImportError as exception: # pragma: no cover
+ 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
diff --git a/src/virtualenv/interpreters/create/venv.py b/src/virtualenv/interpreters/create/venv.py
index 6c71d94..e80a45e 100644
--- a/src/virtualenv/interpreters/create/venv.py
+++ b/src/virtualenv/interpreters/create/venv.py
@@ -47,7 +47,7 @@ class Venv(ViaGlobalRef):
raise ProcessCallFailed(code, out, err, cmd)
def get_host_create_cmd(self):
- cmd = [str(self.interpreter.system_executable), "-m", "venv", "--without-pip"]
+ cmd = [self.interpreter.system_executable, "-m", "venv", "--without-pip"]
if self.system_site_package:
cmd.append("--system-site-packages")
cmd.append("--symlinks" if self.symlinks else "--copies")
diff --git a/src/virtualenv/interpreters/discovery/builtin.py b/src/virtualenv/interpreters/discovery/builtin.py
index 1dc80c0..166066d 100644
--- a/src/virtualenv/interpreters/discovery/builtin.py
+++ b/src/virtualenv/interpreters/discovery/builtin.py
@@ -4,7 +4,7 @@ import logging
import os
import sys
-from pathlib2 import Path
+import six
from virtualenv.info import IS_WIN
@@ -64,7 +64,10 @@ def propose_interpreters(spec):
yield interpreter, True
paths = get_paths()
- for path in paths: # find on path, the path order matters (as the candidates are less easy to control by end user)
+ # find on path, the path order matters (as the candidates are less easy to control by end user)
+ for pos, path in enumerate(paths):
+ path = six.ensure_text(path)
+ logging.debug(LazyPathDump(pos, path))
for candidate, match in possible_specs(spec):
found = check_path(candidate, path)
if found is not None:
@@ -85,31 +88,25 @@ def get_paths():
paths = []
else:
paths = [p for p in path.split(os.pathsep) if os.path.exists(p)]
- logging.debug(LazyPathDump(paths))
return paths
class LazyPathDump(object):
- def __init__(self, paths):
- self.paths = paths
+ def __init__(self, pos, path):
+ self.pos = pos
+ self.path = path
def __str__(self):
- content = "PATH =>{}".format(os.linesep)
- for i, p in enumerate(self.paths):
- files = []
- for file in Path(p).iterdir():
- try:
- if file.is_dir():
- continue
- except OSError:
- pass
- files.append(file.name)
- content += str(i)
+ content = "discover from PATH[{}]:{} with =>".format(self.pos, self.path)
+ for file_name in os.listdir(self.path):
+ try:
+ file_path = os.path.join(self.path, file_name)
+ if os.path.isdir(file_path) or not os.access(file_path, os.X_OK):
+ continue
+ except OSError:
+ pass
content += " "
- content += str(p)
- content += " with "
- content += " ".join(files)
- content += os.linesep
+ content += file_name
return content
diff --git a/src/virtualenv/interpreters/discovery/py_info.py b/src/virtualenv/interpreters/discovery/py_info.py
index 2af2394..13e9b79 100644
--- a/src/virtualenv/interpreters/discovery/py_info.py
+++ b/src/virtualenv/interpreters/discovery/py_info.py
@@ -10,12 +10,9 @@ import json
import logging
import os
import platform
-import subprocess
import sys
from collections import OrderedDict, namedtuple
-IS_WIN = sys.platform == "win32"
-
VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])
@@ -205,12 +202,14 @@ class PythonInfo(object):
@classmethod
def _load_for_exe(cls, exe):
+ from virtualenv.util.subprocess import subprocess, Popen
+
path = "{}.py".format(os.path.splitext(__file__)[0])
cmd = [exe, path]
# noinspection DuplicatedCode
# this is duplicated here because this file is executed on its own, so cannot be refactored otherwise
try:
- process = subprocess.Popen(
+ process = Popen(
cmd, universal_newlines=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE
)
out, err = process.communicate()
diff --git a/src/virtualenv/pyenv_cfg.py b/src/virtualenv/pyenv_cfg.py
index 06a8c81..05daa5a 100644
--- a/src/virtualenv/pyenv_cfg.py
+++ b/src/virtualenv/pyenv_cfg.py
@@ -2,6 +2,8 @@ from __future__ import absolute_import, unicode_literals
import logging
+import six
+
class PyEnvCfg(object):
def __init__(self, content, path):
@@ -20,7 +22,7 @@ class PyEnvCfg(object):
@staticmethod
def _read_values(path):
content = {}
- for line in path.read_text().splitlines():
+ for line in path.read_text(encoding="utf-8").splitlines():
equals_at = line.index("=")
key = line[:equals_at].strip()
value = line[equals_at + 1 :].strip()
@@ -28,13 +30,13 @@ class PyEnvCfg(object):
return content
def write(self):
- with open(str(self.path), "wt") as file_handler:
- logging.debug("write %s", self.path)
+ with open(six.ensure_text(str(self.path)), "wb") as file_handler:
+ logging.debug("write %s", six.ensure_text(str(self.path)))
for key, value in self.content.items():
line = "{} = {}".format(key, value)
logging.debug("\t%s", line)
- file_handler.write(line)
- file_handler.write("\n")
+ file_handler.write(line.encode("utf-8"))
+ file_handler.write(b"\n")
def refresh(self):
self.content = self._read_values(self.path)
diff --git a/src/virtualenv/seed/embed/link_app_data.py b/src/virtualenv/seed/embed/link_app_data.py
index c400da7..d660574 100644
--- a/src/virtualenv/seed/embed/link_app_data.py
+++ b/src/virtualenv/seed/embed/link_app_data.py
@@ -9,10 +9,10 @@ import zipfile
from shutil import copytree
from textwrap import dedent
-from pathlib2 import Path
from six import PY3
from virtualenv.info import get_default_data_dir
+from virtualenv.util import Path
from .base_embed import BaseEmbed
from .wheels.acquire import get_wheel
@@ -172,7 +172,7 @@ def create_console_entry_point(bin_dir, name, value, env_exe, creator):
"{}-{}.{}".format(name, version.major, version.minor),
):
exe = bin_dir / new_name
- exe.write_text(content)
+ exe.write_text(content, encoding="utf-8")
exe.chmod(0o755)
result.append(exe)
return result
@@ -210,7 +210,7 @@ def fix_records(creator, dist_info, site_package, folder_linked, added, extra_fi
record = dist_info / "RECORD"
content = ("" if folder_linked else record.read_text()) + "\n".join(new_records)
- record.write_text(content)
+ record.write_text(content, encoding="utf-8")
def add_record_line(name):
diff --git a/src/virtualenv/seed/embed/pip_invoke.py b/src/virtualenv/seed/embed/pip_invoke.py
index 4de49f3..d65e89a 100644
--- a/src/virtualenv/seed/embed/pip_invoke.py
+++ b/src/virtualenv/seed/embed/pip_invoke.py
@@ -1,10 +1,10 @@
from __future__ import absolute_import, unicode_literals
import os
-import subprocess
from virtualenv.seed.embed.base_embed import BaseEmbed
from virtualenv.seed.embed.wheels.acquire import get_bundled_wheel
+from virtualenv.util.subprocess import Popen
class PipInvoke(BaseEmbed):
@@ -39,4 +39,5 @@ class PipInvoke(BaseEmbed):
}
)
- subprocess.call(cmd, env=env)
+ process = Popen(cmd, env=env)
+ process.communicate()
diff --git a/src/virtualenv/seed/embed/wheels/acquire.py b/src/virtualenv/seed/embed/wheels/acquire.py
index a04c1b8..6460b31 100644
--- a/src/virtualenv/seed/embed/wheels/acquire.py
+++ b/src/virtualenv/seed/embed/wheels/acquire.py
@@ -1,11 +1,11 @@
"""Bootstrap"""
from __future__ import absolute_import, unicode_literals
-import subprocess
from collections import defaultdict
from shutil import copy2
-from pathlib2 import Path
+from virtualenv.util import Path
+from virtualenv.util.subprocess import subprocess
from . import BUNDLE_SUPPORT, MAX
diff --git a/src/virtualenv/util.py b/src/virtualenv/util/__init__.py
index 09c38e6..9e7d13b 100644
--- a/src/virtualenv/util.py
+++ b/src/virtualenv/util/__init__.py
@@ -3,17 +3,20 @@ from __future__ import absolute_import, unicode_literals
import logging
import os
import shutil
-import subprocess
from functools import partial
from os import makedirs
import six
+from virtualenv.util.subprocess import Popen, subprocess
+
+from .path import Path
+
def ensure_dir(path):
if not path.exists():
- logging.debug("created %s", path)
- makedirs(six.text_type(path))
+ logging.debug("created %s", six.ensure_text(str(path)))
+ makedirs(six.ensure_text(str(path)))
HAS_SYMLINK = hasattr(os, "symlink")
@@ -30,21 +33,26 @@ def symlink_or_copy(do_copy, src, dst, relative_symlinks_ok=False):
if not dst.is_symlink(): # can't link to itself!
if relative_symlinks_ok:
assert src.parent == dst.parent
- os.symlink(src.name, six.text_type(dst))
+ os.symlink(six.ensure_text(src.name), six.ensure_text(str(dst)))
else:
- os.symlink(six.text_type(src), six.text_type(dst))
+ os.symlink(six.ensure_text(str(src)), six.ensure_text(str(dst)))
except OSError as exception:
- logging.warning("symlink failed %r, for %s to %s, will try copy", exception, src, dst)
+ logging.warning(
+ "symlink failed %r, for %s to %s, will try copy",
+ exception,
+ six.ensure_text(str(src)),
+ six.ensure_text(str(dst)),
+ )
do_copy = True
if do_copy:
copier = shutil.copy2 if src.is_file() else shutil.copytree
- copier(six.text_type(src), six.text_type(dst))
- logging.debug("%s %s to %s", "copy" if do_copy else "symlink", src, dst)
+ copier(six.ensure_text(str(src)), six.ensure_text(str(dst)))
+ logging.debug("%s %s to %s", "copy" if do_copy else "symlink", six.ensure_text(str(src)), six.ensure_text(str(dst)))
def run_cmd(cmd):
try:
- process = subprocess.Popen(
+ process = Popen(
cmd, universal_newlines=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE
)
out, err = process.communicate() # input disabled
@@ -56,3 +64,5 @@ def run_cmd(cmd):
symlink = partial(symlink_or_copy, False)
copy = partial(symlink_or_copy, True)
+
+__all__ = ("Path", "symlink", "copy", "run_cmd", "ensure_dir")
diff --git a/src/virtualenv/util/path.py b/src/virtualenv/util/path.py
new file mode 100644
index 0000000..5ef5186
--- /dev/null
+++ b/src/virtualenv/util/path.py
@@ -0,0 +1,137 @@
+import sys
+from contextlib import contextmanager
+
+import six
+
+if six.PY3:
+ from pathlib import Path
+
+ if sys.version_info[0:2] == (3, 4):
+ # no read/write text on python3.4
+ BuiltinPath = Path
+
+ class Path(type(BuiltinPath())):
+ def read_text(self, encoding=None, errors=None):
+ """
+ Open the file in text mode, read it, and close the file.
+ """
+ with self.open(mode="r", encoding=encoding, errors=errors) as f:
+ return f.read()
+
+ def write_text(self, data, encoding=None, errors=None):
+ """
+ Open the file in text mode, write to it, and close the file.
+ """
+ if not isinstance(data, str):
+ raise TypeError("data must be str, not %s" % data.__class__.__name__)
+ with self.open(mode="w", encoding=encoding, errors=errors) as f:
+ return f.write(data)
+
+
+else:
+ if sys.platform == "win32":
+ # workaround for https://github.com/mcmtroffaes/pathlib2/issues/56
+ import os
+
+ class Path(object):
+ def __init__(self, path):
+ self._path = path._path if isinstance(path, Path) else six.ensure_text(path)
+
+ def __repr__(self):
+ return six.ensure_str(u"Path({})".format(self._path))
+
+ def __str__(self):
+ return six.ensure_str(self._path)
+
+ def __div__(self, other):
+ return Path(
+ os.path.join(self._path, other._path if isinstance(other, Path) else six.ensure_text(other))
+ )
+
+ def __eq__(self, other):
+ return self._path == (other._path if isinstance(other, Path) else None)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __hash__(self):
+ return hash(self._path)
+
+ def exists(self):
+ return os.path.exists(self._path)
+
+ def absolute(self):
+ return Path(os.path.abspath(self._path))
+
+ @property
+ def parent(self):
+ return Path(os.path.abspath(os.path.join(self._path, os.path.pardir)))
+
+ def resolve(self):
+ return Path(os.path.realpath(self._path))
+
+ @property
+ def name(self):
+ return os.path.basename(self._path)
+
+ @property
+ def parts(self):
+ return self._path.split(os.sep)
+
+ def is_file(self):
+ return os.path.isfile(self._path)
+
+ def is_dir(self):
+ return os.path.isdir(self._path)
+
+ def mkdir(self, parents=True, exist_ok=True):
+ if not self.exists() and exist_ok:
+ os.makedirs(self._path)
+
+ def read_text(self, encoding="utf-8"):
+ with open(self._path, "rb") as file_handler:
+ return file_handler.read().decode(encoding)
+
+ def write_text(self, text, encoding="utf-8"):
+ with open(self._path, "wb") as file_handler:
+ file_handler.write(text.encode(encoding))
+
+ def iterdir(self):
+ for p in os.listdir(self._path):
+ yield Path(os.path.join(self._path, p))
+
+ @property
+ def suffix(self):
+ _, ext = os.path.splitext(self.name)
+ return ext
+
+ @property
+ def stem(self):
+ base, _ = os.path.splitext(self.name)
+ return base
+
+ @contextmanager
+ def open(self, mode="r"):
+ with open(self._path, mode) as file_handler:
+ yield file_handler
+
+ @property
+ def parents(self):
+ result = []
+ parts = self.parts
+ for i in range(len(parts)):
+ result.append(Path(os.sep.join(parts[0 : i + 1])))
+ return result
+
+ def unlink(self):
+ os.remove(self._path)
+
+ def with_name(self, name):
+ return self.parent / name
+
+ def is_symlink(self):
+ return os.path.islink(self._path)
+
+ else:
+ from pathlib2 import Path
+__all__ = ("Path",)
diff --git a/src/virtualenv/util/subprocess/__init__.py b/src/virtualenv/util/subprocess/__init__.py
new file mode 100644
index 0000000..a980cae
--- /dev/null
+++ b/src/virtualenv/util/subprocess/__init__.py
@@ -0,0 +1,15 @@
+from __future__ import absolute_import, unicode_literals
+
+import subprocess
+import sys
+
+import six
+
+if six.PY2 and sys.platform == "win32":
+ from . import win_subprocess
+
+ Popen = win_subprocess.Popen
+else:
+ Popen = subprocess.Popen
+
+__all__ = ("subprocess", "Popen")
diff --git a/src/virtualenv/util/subprocess/win_subprocess.py b/src/virtualenv/util/subprocess/win_subprocess.py
new file mode 100644
index 0000000..e8fdaf0
--- /dev/null
+++ b/src/virtualenv/util/subprocess/win_subprocess.py
@@ -0,0 +1,153 @@
+# flake8: noqa
+# fmt: off
+## issue: https://bugs.python.org/issue19264
+
+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
+
+import _subprocess
+
+##
+## Types
+##
+
+CREATE_UNICODE_ENVIRONMENT = 0x00000400
+LPCTSTR = c_char_p
+LPTSTR = c_wchar_p
+LPSECURITY_ATTRIBUTES = c_void_p
+LPBYTE = ctypes.POINTER(BYTE)
+
+class STARTUPINFOW(Structure):
+ _fields_ = [
+ ("cb", DWORD), ("lpReserved", LPWSTR),
+ ("lpDesktop", LPWSTR), ("lpTitle", LPWSTR),
+ ("dwX", DWORD), ("dwY", DWORD),
+ ("dwXSize", DWORD), ("dwYSize", DWORD),
+ ("dwXCountChars", DWORD), ("dwYCountChars", DWORD),
+ ("dwFillAtrribute", DWORD), ("dwFlags", DWORD),
+ ("wShowWindow", WORD), ("cbReserved2", WORD),
+ ("lpReserved2", LPBYTE), ("hStdInput", HANDLE),
+ ("hStdOutput", HANDLE), ("hStdError", HANDLE),
+ ]
+
+LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW)
+
+
+class PROCESS_INFORMATION(Structure):
+ _fields_ = [
+ ("hProcess", HANDLE), ("hThread", HANDLE),
+ ("dwProcessId", DWORD), ("dwThreadId", DWORD),
+ ]
+
+LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)
+
+
+class DUMMY_HANDLE(ctypes.c_void_p):
+
+ def __init__(self, *a, **kw):
+ super(DUMMY_HANDLE, self).__init__(*a, **kw)
+ self.closed = False
+
+ def Close(self):
+ if not self.closed:
+ windll.kernel32.CloseHandle(self)
+ self.closed = True
+
+ def __int__(self):
+ return self.value
+
+
+CreateProcessW = windll.kernel32.CreateProcessW
+CreateProcessW.argtypes = [
+ LPCTSTR, LPTSTR, LPSECURITY_ATTRIBUTES,
+ LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCTSTR,
+ LPSTARTUPINFOW, LPPROCESS_INFORMATION,
+]
+CreateProcessW.restype = BOOL
+
+
+##
+## Patched functions/classes
+##
+
+def CreateProcess(executable, args, _p_attr, _t_attr,
+ inherit_handles, creation_flags, env, cwd,
+ startup_info):
+ """Create a process supporting unicode executable and args for win32
+
+ Python implementation of CreateProcess using CreateProcessW for Win32
+
+ """
+
+ si = STARTUPINFOW(
+ dwFlags=startup_info.dwFlags,
+ wShowWindow=startup_info.wShowWindow,
+ cb=sizeof(STARTUPINFOW),
+ ## XXXvlab: not sure of the casting here to ints.
+ hStdInput=startup_info.hStdInput if startup_info.hStdInput is None else int(startup_info.hStdInput),
+ hStdOutput=startup_info.hStdOutput if startup_info.hStdOutput is None else int(startup_info.hStdOutput),
+ hStdError=startup_info.hStdError if startup_info.hStdError is None else int(startup_info.hStdError),
+ )
+
+ wenv = None
+ if env is not None:
+ ## LPCWSTR seems to be c_wchar_p, so let's say CWSTR is c_wchar
+ env = (unicode("").join([
+ unicode("%s=%s\0") % (k, v)
+ for k, v in env.items()])) + unicode("\0")
+ wenv = (c_wchar * len(env))()
+ wenv.value = env
+
+ pi = PROCESS_INFORMATION()
+ creation_flags |= CREATE_UNICODE_ENVIRONMENT
+
+ if CreateProcessW(executable, args, None, None,
+ inherit_handles, creation_flags,
+ wenv, cwd, byref(si), byref(pi)):
+ return (DUMMY_HANDLE(pi.hProcess), DUMMY_HANDLE(pi.hThread),
+ pi.dwProcessId, pi.dwThreadId)
+ raise WinError()
+
+
+class Popen(subprocess.Popen):
+ """This superseeds Popen and corrects a bug in cPython 2.7 implem"""
+
+ def _execute_child(self, args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell, to_close,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
+ """Code from part of _execute_child from Python 2.7 (9fbb65e)
+
+ There are only 2 little changes concerning the construction of
+ the the final string in shell mode: we preempt the creation of
+ the command string when shell is True, because original function
+ will try to encode unicode args which we want to avoid to be able to
+ sending it as-is to ``CreateProcess``.
+
+ """
+ if startupinfo is None:
+ startupinfo = subprocess.STARTUPINFO()
+ if not isinstance(args, subprocess.types.StringTypes):
+ args = subprocess.list2cmdline(args)
+ startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
+ startupinfo.wShowWindow = _subprocess.SW_HIDE
+ comspec = os.environ.get("COMSPEC", unicode("cmd.exe"))
+ if (_subprocess.GetVersion() >= 0x80000000 or
+ os.path.basename(comspec).lower() == "command.com"):
+ w9xpopen = self._find_w9xpopen()
+ args = unicode('"%s" %s') % (w9xpopen, args)
+ creationflags |= _subprocess.CREATE_NEW_CONSOLE
+
+ super(Popen, self)._execute_child(args, executable,
+ preexec_fn, close_fds, cwd, env, universal_newlines,
+ startupinfo, creationflags, False, to_close, p2cread,
+ p2cwrite, c2pread, c2pwrite, errread, errwrite)
+
+_subprocess.CreateProcess = CreateProcess
+# fmt: on