Viewing file: base.py (6.89 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
from __future__ import absolute_import, unicode_literals
import logging import os import re import zipfile from abc import ABCMeta, abstractmethod from contextlib import contextmanager from tempfile import mkdtemp from threading import Lock
# noinspection PyProtectedMember from distlib.scripts import ScriptMaker, _enquote_executable from six import PY3, add_metaclass
from virtualenv.util import ConfigParser from virtualenv.util.path import Path, safe_delete from virtualenv.util.six import ensure_text
@add_metaclass(ABCMeta) class PipInstall(object): lock = Lock()
def __init__(self, wheel, creator, image_folder): 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 # sync image for filename in self._image_dir.iterdir(): into = self._creator.purelib / filename.name if into.exists(): if into.is_dir() and not into.is_symlink(): safe_delete(into) else: into.unlink() 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 to %s of %s", self._image_dir, self._wheel.name) with zipfile.ZipFile(str(self._wheel)) as zip_ref: zip_ref.extractall(str(self._image_dir)) self._extracted = True # 2. now add additional files not present in the package new_files = self._generate_new_files() # 3. finally fix the records file self._fix_records(new_files)
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 ) return record_data
def _generate_new_files(self): new_files = set() installer = self._dist_info / "INSTALLER" installer.write_text("pip\n") 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 / "{}.virtualenv".format(self._dist_info.stem) marker.write_text("") new_files.add(marker) folder = mkdtemp() try: to_folder = Path(folder) rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(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(ensure_text(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: for filename in self._image_dir.iterdir(): if filename.suffix == ".dist-info": self.__dist_info = filename break else: raise RuntimeError("no dist info") # 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.ConfigParser() with entry_points.open() as file_handler: reader = getattr(parser, "read_file" if PY3 else "readfp") reader(file_handler) if "console_scripts" in parser.sections(): for name, value in parser.items("console_scripts"): match = re.match(r"(.*?)-?\d\.?\d*", name) if match: name = match.groups(1)[0] self._console_entry_points[name] = value return self._console_entry_points
def _create_console_entry_point(self, name, value, to_folder, version_info): result = [] maker = ScriptMaker(None, str(to_folder)) maker.clobber = True # overwrite maker.variants = {""} # set within patch_distlib_correct_variants maker.set_mode = True # ensure they are executable # calling private until https://bitbucket.org/pypa/distlib/issues/135/expose-_enquote_executable-as-public maker.executable = _enquote_executable(str(self._creator.exe)) specification = "{} = {}".format(name, value) with self.patch_distlib_correct_variants(version_info, maker): new_files = maker.make(specification) result.extend(Path(i) for i in new_files) return result
@contextmanager def patch_distlib_correct_variants(self, version_info, maker): """ Patch until upstream distutils supports creating scripts with different python target https://bitbucket.org/pypa/distlib/issues/134/allow-specifying-the-version-information """
def _write_script(scriptnames, shebang, script, filenames, ext): name = next(iter(scriptnames)) scriptnames = { # add our variants '', 'X', '-X.Y' name, "{}{}".format(name, version_info.major), "{}-{}.{}".format(name, version_info.major, version_info.minor), } if name == "pip": scriptnames.add("{}{}.{}".format(name, version_info.major, version_info.minor)) return previous(scriptnames, shebang, script, filenames, ext)
previous = maker._write_script with self.lock: maker._write_script = _write_script try: yield finally: maker._write_script = previous
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
|