Skip to content

Commit

Permalink
Shorten path on image build if needed (Windows with no long path supp…
Browse files Browse the repository at this point in the history
…ort)
  • Loading branch information
gaborbernat committed Dec 23, 2020
1 parent 88101a4 commit 39e4985
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 3 deletions.
15 changes: 15 additions & 0 deletions src/virtualenv/seed/embed/via_app_data/pip_install/base.py
Expand Up @@ -51,13 +51,28 @@ def build_image(self):
# 1. first extract the wheel
logging.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
with zipfile.ZipFile(str(self._wheel)) as zip_ref:
self._shorten_path_if_needed(zip_ref)
zip_ref.extractall(str(self._image_dir))
self._extracted = True
# 2. now add additional files not present in the distribution
new_files = self._generate_new_files()
# 3. finally fix the records file
self._fix_records(new_files)

def _shorten_path_if_needed(self, zip_ref):
if os.name == "nt":
to_folder = str(self._image_dir)
# https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
zip_max_len = max(len(i) for i in zip_ref.namelist())
path_len = zip_max_len + len(to_folder)
if path_len > 260:
self._image_dir.mkdir(exist_ok=True) # to get a short path must exist

from virtualenv.util.path import get_short_path_name

to_folder = get_short_path_name(to_folder)
self._image_dir = Path(to_folder)

def _records_text(self, files):
record_data = "\n".join(
"{},,".format(os.path.relpath(ensure_text(str(rec)), ensure_text(str(self._image_dir)))) for rec in files
Expand Down
2 changes: 2 additions & 0 deletions src/virtualenv/util/path/__init__.py
Expand Up @@ -3,6 +3,7 @@
from ._pathlib import Path
from ._permission import make_exe, set_tree
from ._sync import copy, copytree, ensure_dir, safe_delete, symlink
from ._win import get_short_path_name

__all__ = (
"ensure_dir",
Expand All @@ -13,4 +14,5 @@
"make_exe",
"set_tree",
"safe_delete",
"get_short_path_name",
)
19 changes: 19 additions & 0 deletions src/virtualenv/util/path/_win.py
@@ -0,0 +1,19 @@
def get_short_path_name(long_name):
"""
Gets the short path name of a given long path.
http://stackoverflow.com/a/23598461/200291
"""
import ctypes
from ctypes import wintypes

_GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
_GetShortPathNameW.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
_GetShortPathNameW.restype = wintypes.DWORD
output_buf_size = 0
while True:
output_buf = ctypes.create_unicode_buffer(output_buf_size)
needed = _GetShortPathNameW(long_name, output_buf, output_buf_size)
if output_buf_size >= needed:
return output_buf.value
else:
output_buf_size = needed
3 changes: 0 additions & 3 deletions tests/conftest.py
Expand Up @@ -335,9 +335,6 @@ def current_fastest(current_creators):
@pytest.fixture(scope="session")
def session_app_data(tmp_path_factory):
temp_folder = tmp_path_factory.mktemp("session-app-data")
# check long path support
test_long_path_support = temp_folder / ("a" * (270 - len(str(temp_folder))))
test_long_path_support.write_text("a")
app_data = AppDataDiskFolder(folder=str(temp_folder))
with change_env_var(str("VIRTUALENV_OVERRIDE_APP_DATA"), str(app_data.lock.path)):
yield app_data
Expand Down

0 comments on commit 39e4985

Please sign in to comment.