Merge pull request #1946 from pypa/bugfix/windows-venv-name

Windows VEnv Case normalization
This commit is contained in:
Dan Ryan
2018-04-11 14:38:09 -04:00
committed by GitHub
2 changed files with 83 additions and 20 deletions
+67 -20
View File
@@ -14,6 +14,11 @@ import pipfile
import pipfile.api
import toml
try:
import pathlib
except ImportError:
import pathlib2 as pathlib
from pip9 import ConfigOptionParser
from .cmdparse import Script
from .utils import (
@@ -41,12 +46,19 @@ from .environments import (
PIPENV_PYTHON,
)
def _normalized(p):
if p is None:
return None
return normalize_drive(str(pathlib.Path(p).resolve()))
if PIPENV_PIPFILE:
if not os.path.isfile(PIPENV_PIPFILE):
raise RuntimeError('Given PIPENV_PIPFILE is not found!')
else:
PIPENV_PIPFILE = normalize_drive(os.path.abspath(PIPENV_PIPFILE))
PIPENV_PIPFILE = _normalized(PIPENV_PIPFILE)
# (path, file contents) => TOMLFile
# keeps track of pipfiles that we've seen so we do not need to re-parse 'em
_pipfile_cache = {}
@@ -203,8 +215,16 @@ class Project(object):
return False
@property
def virtualenv_name(self):
@classmethod
def _get_virtualenv_location(cls, name):
from pipenv.patched.pew.pew import get_workon_home
venv = get_workon_home() / name
if not venv.exists():
return ''
return '{0}'.format(venv)
@classmethod
def _sanitize(cls, name):
# Replace dangerous characters into '_'. The length of the sanitized
# project name is limited as 42 because of the limit of linux kernel
#
@@ -217,17 +237,50 @@ class Project(object):
# https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
# http://www.tldp.org/LDP/abs/html/special-chars.html#FIELDREF
# https://github.com/torvalds/linux/blob/2bfe01ef/include/uapi/linux/binfmts.h#L18
sanitized = re.sub(r'[ $`!*@"\\\r\n\t]', '_', self.name)[0:42]
# Hash the full path of the pipfile
hash = hashlib.sha256(self.pipfile_location.encode()).digest()[:6]
encoded_hash = base64.urlsafe_b64encode(hash).decode()
return re.sub(r'[ $`!*@"\\\r\n\t]', '_', name)[0:42]
def _get_virtualenv_hash(self, name):
"""Get the name of the virtualenv adjusted for windows if needed
Returns (name, encoded_hash)
"""
def get_name(name, location):
name = self._sanitize(name)
hash = hashlib.sha256(location.encode()).digest()[:6]
encoded_hash = base64.urlsafe_b64encode(hash).decode()
return name, encoded_hash[:8]
clean_name, encoded_hash = get_name(name, self.pipfile_location)
venv_name = '{0}-{1}'.format(clean_name, encoded_hash)
# This should work most of the time, for non-WIndows, in-project venv,
# or "proper" path casing (on Windows).
if (os.name != 'nt' or
PIPENV_VENV_IN_PROJECT or
self._get_virtualenv_location(venv_name)):
return clean_name, encoded_hash
# Check for different capitalization of the same project.
from pipenv.patched.pew.pew import lsenvs
for env in lsenvs():
env_name = env[:-9]
if not (env[-9] != '-' and
env[-8:].isalpha() and
env_name.lower() != name.lower()):
continue
return get_name(env_name, self.pipfile_location.replace(name, env_name))
# Use the default if no matching env exists.
return clean_name, encoded_hash
@property
def virtualenv_name(self):
sanitized, encoded_hash = self._get_virtualenv_hash(self.name)
suffix = '-{0}'.format(PIPENV_PYTHON) if PIPENV_PYTHON else ''
# If the pipfile was located at '/home/user/MY_PROJECT/Pipfile',
# the name of its virtualenv will be 'my-project-wyUfYPqE'
if PIPENV_PYTHON:
return sanitized + '-' + encoded_hash + '-' + PIPENV_PYTHON
else:
return sanitized + '-' + encoded_hash
return sanitized + '-' + encoded_hash + suffix
@property
def virtualenv_location(self):
@@ -243,13 +296,7 @@ class Project(object):
# Default mode.
if not venv_in_project:
c = delegator.run(
'{0} -m pipenv.pew dir "{1}"'.format(
escape_grouped_arguments(sys.executable),
self.virtualenv_name,
)
)
loc = c.out.strip()
loc = self._get_virtualenv_location(self.virtualenv_name)
# The user wants the virtualenv in the project.
else:
loc = os.sep.join(
@@ -304,7 +351,7 @@ class Project(object):
loc = pipfile.Pipfile.find(max_depth=PIPENV_MAX_DEPTH)
except RuntimeError:
loc = None
self._pipfile_location = normalize_drive(loc)
self._pipfile_location = _normalized(loc)
return self._pipfile_location
@property
+16
View File
@@ -1356,3 +1356,19 @@ multicommand = "bash -c \"cd docs && make html\""
yarl = p.lockfile['default']['yarl']
assert 'markers' in yarl
assert yarl['markers'] == "python_version in '3.4, 3.5, 3.6'"
@pytest.mark.project
@pytest.mark.skipif(os.name != 'nt', reason='Test project matching for case changes on win')
def test_case_changes_windows(self, pypi):
with PipenvInstance(pypi=pypi, chdir=True) as p:
c = p.pipenv('install pytz')
assert c.return_code == 0
virtualenv_location = Project().virtualenv_location
target = p.path.upper()
if target == p.path:
target = p.path.lower()
os.chdir('..')
os.chdir(target)
assert os.path.abspath(os.curdir) != p.path
venv = delegator.run('pipenv --venv').out
assert venv.strip().lower() == virtualenv_location.lower()