first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,118 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.seed.seeder import Seeder
|
||||
from virtualenv.seed.wheels import Version
|
||||
|
||||
PERIODIC_UPDATE_ON_BY_DEFAULT = True
|
||||
|
||||
|
||||
class BaseEmbed(Seeder, ABC):
|
||||
def __init__(self, options) -> None:
|
||||
super().__init__(options, enabled=options.no_seed is False)
|
||||
|
||||
self.download = options.download
|
||||
self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]
|
||||
|
||||
self.pip_version = options.pip
|
||||
self.setuptools_version = options.setuptools
|
||||
self.wheel_version = options.wheel
|
||||
|
||||
self.no_pip = options.no_pip
|
||||
self.no_setuptools = options.no_setuptools
|
||||
self.no_wheel = options.no_wheel
|
||||
self.app_data = options.app_data
|
||||
self.periodic_update = not options.no_periodic_update
|
||||
|
||||
if not self.distribution_to_versions():
|
||||
self.enabled = False
|
||||
|
||||
@classmethod
|
||||
def distributions(cls) -> dict[str, Version]:
|
||||
return {
|
||||
"pip": Version.bundle,
|
||||
"setuptools": Version.bundle,
|
||||
"wheel": Version.bundle,
|
||||
}
|
||||
|
||||
def distribution_to_versions(self) -> dict[str, str]:
|
||||
return {
|
||||
distribution: getattr(self, f"{distribution}_version")
|
||||
for distribution in self.distributions()
|
||||
if getattr(self, f"no_{distribution}") is False and getattr(self, f"{distribution}_version") != "none"
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, parser, interpreter, app_data): # noqa: ARG003
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"--no-download",
|
||||
"--never-download",
|
||||
dest="download",
|
||||
action="store_false",
|
||||
help=f"pass to disable download of the latest {'/'.join(cls.distributions())} from PyPI",
|
||||
default=True,
|
||||
)
|
||||
group.add_argument(
|
||||
"--download",
|
||||
dest="download",
|
||||
action="store_true",
|
||||
help=f"pass to enable download of the latest {'/'.join(cls.distributions())} from PyPI",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--extra-search-dir",
|
||||
metavar="d",
|
||||
type=Path,
|
||||
nargs="+",
|
||||
help="a path containing wheels to extend the internal wheel list (can be set 1+ times)",
|
||||
default=[],
|
||||
)
|
||||
for distribution, default in cls.distributions().items():
|
||||
if interpreter.version_info[:2] >= (3, 12) and distribution in {"wheel", "setuptools"}:
|
||||
default = "none" # noqa: PLW2901
|
||||
parser.add_argument(
|
||||
f"--{distribution}",
|
||||
dest=distribution,
|
||||
metavar="version",
|
||||
help=f"version of {distribution} to install as seed: embed, bundle, none or exact version",
|
||||
default=default,
|
||||
)
|
||||
for distribution in cls.distributions():
|
||||
parser.add_argument(
|
||||
f"--no-{distribution}",
|
||||
dest=f"no_{distribution}",
|
||||
action="store_true",
|
||||
help=f"do not install {distribution}",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-periodic-update",
|
||||
dest="no_periodic_update",
|
||||
action="store_true",
|
||||
help="disable the periodic (once every 14 days) update of the embedded wheels",
|
||||
default=not PERIODIC_UPDATE_ON_BY_DEFAULT,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
result = self.__class__.__name__
|
||||
result += "("
|
||||
if self.extra_search_dir:
|
||||
result += f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)},"
|
||||
result += f"download={self.download},"
|
||||
for distribution in self.distributions():
|
||||
if getattr(self, f"no_{distribution}"):
|
||||
continue
|
||||
version = getattr(self, f"{distribution}_version", None)
|
||||
if version == "none":
|
||||
continue
|
||||
ver = f"={version or 'latest'}"
|
||||
result += f" {distribution}{ver},"
|
||||
return result[:-1] + ")"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BaseEmbed",
|
||||
]
|
|
@ -0,0 +1,63 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from subprocess import Popen
|
||||
|
||||
from virtualenv.discovery.cached_py_info import LogCmd
|
||||
from virtualenv.seed.embed.base_embed import BaseEmbed
|
||||
from virtualenv.seed.wheels import Version, get_wheel, pip_wheel_env_run
|
||||
|
||||
|
||||
class PipInvoke(BaseEmbed):
|
||||
def __init__(self, options) -> None:
|
||||
super().__init__(options)
|
||||
|
||||
def run(self, creator):
|
||||
if not self.enabled:
|
||||
return
|
||||
for_py_version = creator.interpreter.version_release_str
|
||||
with self.get_pip_install_cmd(creator.exe, for_py_version) as cmd:
|
||||
env = pip_wheel_env_run(self.extra_search_dir, self.app_data, self.env)
|
||||
self._execute(cmd, env)
|
||||
|
||||
@staticmethod
|
||||
def _execute(cmd, env):
|
||||
logging.debug("pip seed by running: %s", LogCmd(cmd, env))
|
||||
process = Popen(cmd, env=env)
|
||||
process.communicate()
|
||||
if process.returncode != 0:
|
||||
msg = f"failed seed with code {process.returncode}"
|
||||
raise RuntimeError(msg)
|
||||
return process
|
||||
|
||||
@contextmanager
|
||||
def get_pip_install_cmd(self, exe, for_py_version):
|
||||
cmd = [str(exe), "-m", "pip", "-q", "install", "--only-binary", ":all:", "--disable-pip-version-check"]
|
||||
if not self.download:
|
||||
cmd.append("--no-index")
|
||||
folders = set()
|
||||
for dist, version in self.distribution_to_versions().items():
|
||||
wheel = get_wheel(
|
||||
distribution=dist,
|
||||
version=version,
|
||||
for_py_version=for_py_version,
|
||||
search_dirs=self.extra_search_dir,
|
||||
download=False,
|
||||
app_data=self.app_data,
|
||||
do_periodic_update=self.periodic_update,
|
||||
env=self.env,
|
||||
)
|
||||
if wheel is None:
|
||||
msg = f"could not get wheel for distribution {dist}"
|
||||
raise RuntimeError(msg)
|
||||
folders.add(str(wheel.path.parent))
|
||||
cmd.append(Version.as_pip_req(dist, wheel.version))
|
||||
for folder in sorted(folders):
|
||||
cmd.extend(["--find-links", str(folder)])
|
||||
yield cmd
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PipInvoke",
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,204 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import zipfile
|
||||
from abc import ABC, abstractmethod
|
||||
from configparser import ConfigParser
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from distlib.scripts import ScriptMaker, enquote_executable
|
||||
|
||||
from virtualenv.util.path import safe_delete
|
||||
|
||||
|
||||
class PipInstall(ABC):
|
||||
def __init__(self, wheel, creator, image_folder) -> None:
|
||||
self._wheel = wheel
|
||||
self._creator = creator
|
||||
self._image_dir = image_folder
|
||||
self._extracted = False
|
||||
self.__dist_info = None
|
||||
self._console_entry_points = None
|
||||
|
||||
@abstractmethod
|
||||
def _sync(self, src, dst):
|
||||
raise NotImplementedError
|
||||
|
||||
def install(self, version_info):
|
||||
self._extracted = True
|
||||
self._uninstall_previous_version()
|
||||
# sync image
|
||||
for filename in self._image_dir.iterdir():
|
||||
into = self._creator.purelib / filename.name
|
||||
self._sync(filename, into)
|
||||
# generate console executables
|
||||
consoles = set()
|
||||
script_dir = self._creator.script_dir
|
||||
for name, module in self._console_scripts.items():
|
||||
consoles.update(self._create_console_entry_point(name, module, script_dir, version_info))
|
||||
logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
|
||||
|
||||
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: # noqa: PLR2004
|
||||
self._image_dir.mkdir(exist_ok=True) # to get a short path must exist
|
||||
|
||||
from virtualenv.util.path import get_short_path_name # noqa: PLC0415
|
||||
|
||||
to_folder = get_short_path_name(to_folder)
|
||||
self._image_dir = Path(to_folder)
|
||||
|
||||
def _records_text(self, files):
|
||||
return "\n".join(f"{os.path.relpath(str(rec), str(self._image_dir))},," for rec in files)
|
||||
|
||||
def _generate_new_files(self):
|
||||
new_files = set()
|
||||
installer = self._dist_info / "INSTALLER"
|
||||
installer.write_text("pip\n", encoding="utf-8")
|
||||
new_files.add(installer)
|
||||
# inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
|
||||
marker = self._image_dir / f"{self._dist_info.stem}.virtualenv"
|
||||
marker.write_text("", encoding="utf-8")
|
||||
new_files.add(marker)
|
||||
folder = mkdtemp()
|
||||
try:
|
||||
to_folder = Path(folder)
|
||||
rel = os.path.relpath(str(self._creator.script_dir), str(self._creator.purelib))
|
||||
version_info = self._creator.interpreter.version_info
|
||||
for name, module in self._console_scripts.items():
|
||||
new_files.update(
|
||||
Path(os.path.normpath(str(self._image_dir / rel / i.name)))
|
||||
for i in self._create_console_entry_point(name, module, to_folder, version_info)
|
||||
)
|
||||
finally:
|
||||
safe_delete(folder)
|
||||
return new_files
|
||||
|
||||
@property
|
||||
def _dist_info(self):
|
||||
if self._extracted is False:
|
||||
return None # pragma: no cover
|
||||
if self.__dist_info is None:
|
||||
files = []
|
||||
for filename in self._image_dir.iterdir():
|
||||
files.append(filename.name)
|
||||
if filename.suffix == ".dist-info":
|
||||
self.__dist_info = filename
|
||||
break
|
||||
else:
|
||||
msg = f"no .dist-info at {self._image_dir}, has {', '.join(files)}"
|
||||
raise RuntimeError(msg) # pragma: no cover
|
||||
return self.__dist_info
|
||||
|
||||
@abstractmethod
|
||||
def _fix_records(self, extra_record_data):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def _console_scripts(self):
|
||||
if self._extracted is False:
|
||||
return None # pragma: no cover
|
||||
if self._console_entry_points is None:
|
||||
self._console_entry_points = {}
|
||||
entry_points = self._dist_info / "entry_points.txt"
|
||||
if entry_points.exists():
|
||||
parser = ConfigParser()
|
||||
with entry_points.open(encoding="utf-8") as file_handler:
|
||||
parser.read_file(file_handler)
|
||||
if "console_scripts" in parser.sections():
|
||||
for name, value in parser.items("console_scripts"):
|
||||
match = re.match(r"(.*?)-?\d\.?\d*", name)
|
||||
our_name = match.groups(1)[0] if match else name
|
||||
self._console_entry_points[our_name] = value
|
||||
return self._console_entry_points
|
||||
|
||||
def _create_console_entry_point(self, name, value, to_folder, version_info):
|
||||
result = []
|
||||
maker = ScriptMakerCustom(to_folder, version_info, self._creator.exe, name)
|
||||
specification = f"{name} = {value}"
|
||||
new_files = maker.make(specification)
|
||||
result.extend(Path(i) for i in new_files)
|
||||
return result
|
||||
|
||||
def _uninstall_previous_version(self):
|
||||
dist_name = self._dist_info.stem.split("-")[0]
|
||||
in_folders = chain.from_iterable([i.iterdir() for i in (self._creator.purelib, self._creator.platlib)])
|
||||
paths = (p for p in in_folders if p.stem.split("-")[0] == dist_name and p.suffix == ".dist-info" and p.is_dir())
|
||||
existing_dist = next(paths, None)
|
||||
if existing_dist is not None:
|
||||
self._uninstall_dist(existing_dist)
|
||||
|
||||
@staticmethod
|
||||
def _uninstall_dist(dist):
|
||||
dist_base = dist.parent
|
||||
logging.debug("uninstall existing distribution %s from %s", dist.stem, dist_base)
|
||||
|
||||
top_txt = dist / "top_level.txt" # add top level packages at folder level
|
||||
paths = (
|
||||
{dist.parent / i.strip() for i in top_txt.read_text(encoding="utf-8").splitlines()}
|
||||
if top_txt.exists()
|
||||
else set()
|
||||
)
|
||||
paths.add(dist) # add the dist-info folder itself
|
||||
|
||||
base_dirs, record = paths.copy(), dist / "RECORD" # collect entries in record that we did not register yet
|
||||
for name in (
|
||||
(i.split(",")[0] for i in record.read_text(encoding="utf-8").splitlines()) if record.exists() else ()
|
||||
):
|
||||
path = dist_base / name
|
||||
if not any(p in base_dirs for p in path.parents): # only add if not already added as a base dir
|
||||
paths.add(path)
|
||||
|
||||
for path in sorted(paths): # actually remove stuff in a stable order
|
||||
if path.exists():
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
safe_delete(path)
|
||||
else:
|
||||
path.unlink()
|
||||
|
||||
def clear(self):
|
||||
if self._image_dir.exists():
|
||||
safe_delete(self._image_dir)
|
||||
|
||||
def has_image(self):
|
||||
return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None
|
||||
|
||||
|
||||
class ScriptMakerCustom(ScriptMaker):
|
||||
def __init__(self, target_dir, version_info, executable, name) -> None:
|
||||
super().__init__(None, str(target_dir))
|
||||
self.clobber = True # overwrite
|
||||
self.set_mode = True # ensure they are executable
|
||||
self.executable = enquote_executable(str(executable))
|
||||
self.version_info = version_info.major, version_info.minor
|
||||
self.variants = {"", "X", "X.Y"}
|
||||
self._name = name
|
||||
|
||||
def _write_script(self, names, shebang, script_bytes, filenames, ext):
|
||||
names.add(f"{self._name}{self.version_info[0]}.{self.version_info[1]}")
|
||||
super()._write_script(names, shebang, script_bytes, filenames, ext)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PipInstall",
|
||||
]
|
|
@ -0,0 +1,40 @@
|
|||
from __future__ import annotations # noqa: A005
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from virtualenv.util.path import copy
|
||||
|
||||
from .base import PipInstall
|
||||
|
||||
|
||||
class CopyPipInstall(PipInstall):
|
||||
def _sync(self, src, dst):
|
||||
copy(src, dst)
|
||||
|
||||
def _generate_new_files(self):
|
||||
# create the pyc files
|
||||
new_files = super()._generate_new_files()
|
||||
new_files.update(self._cache_files())
|
||||
return new_files
|
||||
|
||||
def _cache_files(self):
|
||||
version = self._creator.interpreter.version_info
|
||||
py_c_ext = f".{self._creator.interpreter.implementation.lower()}-{version.major}{version.minor}.pyc"
|
||||
for root, dirs, files in os.walk(str(self._image_dir), topdown=True):
|
||||
root_path = Path(root)
|
||||
for name in files:
|
||||
if name.endswith(".py"):
|
||||
yield root_path / f"{name[:-3]}{py_c_ext}"
|
||||
for name in dirs:
|
||||
yield root_path / name / "__pycache__"
|
||||
|
||||
def _fix_records(self, new_files):
|
||||
extra_record_data_str = self._records_text(new_files)
|
||||
with (self._dist_info / "RECORD").open("ab") as file_handler:
|
||||
file_handler.write(extra_record_data_str.encode("utf-8"))
|
||||
|
||||
|
||||
__all__ = [
|
||||
"CopyPipInstall",
|
||||
]
|
|
@ -0,0 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from stat import S_IREAD, S_IRGRP, S_IROTH
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
from virtualenv.util.path import safe_delete, set_tree
|
||||
|
||||
from .base import PipInstall
|
||||
|
||||
|
||||
class SymlinkPipInstall(PipInstall):
|
||||
def _sync(self, src, dst):
|
||||
os.symlink(str(src), str(dst))
|
||||
|
||||
def _generate_new_files(self):
|
||||
# create the pyc files, as the build image will be R/O
|
||||
cmd = [str(self._creator.exe), "-m", "compileall", str(self._image_dir)]
|
||||
process = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
process.communicate()
|
||||
# the root pyc is shared, so we'll not symlink that - but still add the pyc files to the RECORD for close
|
||||
root_py_cache = self._image_dir / "__pycache__"
|
||||
new_files = set()
|
||||
if root_py_cache.exists():
|
||||
new_files.update(root_py_cache.iterdir())
|
||||
new_files.add(root_py_cache)
|
||||
safe_delete(root_py_cache)
|
||||
core_new_files = super()._generate_new_files()
|
||||
# remove files that are within the image folder deeper than one level (as these will be not linked directly)
|
||||
for file in core_new_files:
|
||||
try:
|
||||
rel = file.relative_to(self._image_dir)
|
||||
if len(rel.parts) > 1:
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
new_files.add(file)
|
||||
return new_files
|
||||
|
||||
def _fix_records(self, new_files):
|
||||
new_files.update(i for i in self._image_dir.iterdir())
|
||||
extra_record_data_str = self._records_text(sorted(new_files, key=str))
|
||||
(self._dist_info / "RECORD").write_text(extra_record_data_str, encoding="utf-8")
|
||||
|
||||
def build_image(self):
|
||||
super().build_image()
|
||||
# protect the image by making it read only
|
||||
set_tree(self._image_dir, S_IREAD | S_IRGRP | S_IROTH)
|
||||
|
||||
def clear(self):
|
||||
if self._image_dir.exists():
|
||||
safe_delete(self._image_dir)
|
||||
super().clear()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"SymlinkPipInstall",
|
||||
]
|
|
@ -0,0 +1,144 @@
|
|||
"""Bootstrap."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from subprocess import CalledProcessError
|
||||
from threading import Lock, Thread
|
||||
|
||||
from virtualenv.info import fs_supports_symlink
|
||||
from virtualenv.seed.embed.base_embed import BaseEmbed
|
||||
from virtualenv.seed.wheels import get_wheel
|
||||
|
||||
from .pip_install.copy import CopyPipInstall
|
||||
from .pip_install.symlink import SymlinkPipInstall
|
||||
|
||||
|
||||
class FromAppData(BaseEmbed):
|
||||
def __init__(self, options) -> None:
|
||||
super().__init__(options)
|
||||
self.symlinks = options.symlink_app_data
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, parser, interpreter, app_data):
|
||||
super().add_parser_arguments(parser, interpreter, app_data)
|
||||
can_symlink = app_data.transient is False and fs_supports_symlink()
|
||||
sym = "" if can_symlink else "not supported - "
|
||||
parser.add_argument(
|
||||
"--symlink-app-data",
|
||||
dest="symlink_app_data",
|
||||
action="store_true" if can_symlink else "store_false",
|
||||
help=f"{sym} symlink the python packages from the app-data folder (requires seed pip>=19.3)",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def run(self, creator):
|
||||
if not self.enabled:
|
||||
return
|
||||
with self._get_seed_wheels(creator) as name_to_whl:
|
||||
pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
|
||||
installer_class = self.installer_class(pip_version)
|
||||
exceptions = {}
|
||||
|
||||
def _install(name, wheel):
|
||||
try:
|
||||
logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
|
||||
key = Path(installer_class.__name__) / wheel.path.stem
|
||||
wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
|
||||
installer = installer_class(wheel.path, creator, wheel_img)
|
||||
parent = self.app_data.lock / wheel_img.parent
|
||||
with parent.non_reentrant_lock_for_key(wheel_img.name):
|
||||
if not installer.has_image():
|
||||
installer.build_image()
|
||||
installer.install(creator.interpreter.version_info)
|
||||
except Exception: # noqa: BLE001
|
||||
exceptions[name] = sys.exc_info()
|
||||
|
||||
threads = [Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items()]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
if exceptions:
|
||||
messages = [f"failed to build image {', '.join(exceptions.keys())} because:"]
|
||||
for value in exceptions.values():
|
||||
exc_type, exc_value, exc_traceback = value
|
||||
messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
|
||||
raise RuntimeError("\n".join(messages))
|
||||
|
||||
@contextmanager
|
||||
def _get_seed_wheels(self, creator): # noqa: C901
|
||||
name_to_whl, lock, fail = {}, Lock(), {}
|
||||
|
||||
def _get(distribution, version):
|
||||
for_py_version = creator.interpreter.version_release_str
|
||||
failure, result = None, None
|
||||
# fallback to download in case the exact version is not available
|
||||
for download in [True] if self.download else [False, True]:
|
||||
failure = None
|
||||
try:
|
||||
result = get_wheel(
|
||||
distribution=distribution,
|
||||
version=version,
|
||||
for_py_version=for_py_version,
|
||||
search_dirs=self.extra_search_dir,
|
||||
download=download,
|
||||
app_data=self.app_data,
|
||||
do_periodic_update=self.periodic_update,
|
||||
env=self.env,
|
||||
)
|
||||
if result is not None:
|
||||
break
|
||||
except Exception as exception:
|
||||
logging.exception("fail")
|
||||
failure = exception
|
||||
if failure:
|
||||
if isinstance(failure, CalledProcessError):
|
||||
msg = f"failed to download {distribution}"
|
||||
if version is not None:
|
||||
msg += f" version {version}"
|
||||
msg += f", pip download exit code {failure.returncode}"
|
||||
output = failure.output + failure.stderr
|
||||
if output:
|
||||
msg += "\n"
|
||||
msg += output
|
||||
else:
|
||||
msg = repr(failure)
|
||||
logging.error(msg)
|
||||
with lock:
|
||||
fail[distribution] = version
|
||||
else:
|
||||
with lock:
|
||||
name_to_whl[distribution] = result
|
||||
|
||||
threads = [
|
||||
Thread(target=_get, args=(distribution, version))
|
||||
for distribution, version in self.distribution_to_versions().items()
|
||||
]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
if fail:
|
||||
msg = f"seed failed due to failing to download wheels {', '.join(fail.keys())}"
|
||||
raise RuntimeError(msg)
|
||||
yield name_to_whl
|
||||
|
||||
def installer_class(self, pip_version_tuple):
|
||||
if self.symlinks and pip_version_tuple and pip_version_tuple >= (19, 3): # symlink support requires pip 19.3+
|
||||
return SymlinkPipInstall
|
||||
return CopyPipInstall
|
||||
|
||||
def __repr__(self) -> str:
|
||||
msg = f", via={'symlink' if self.symlinks else 'copy'}, app_data_dir={self.app_data}"
|
||||
base = super().__repr__()
|
||||
return f"{base[:-1]}{msg}{base[-1]}"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"FromAppData",
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue