1""" 2Virtual environments in the traditional sense are built as reference to the host python. This file allows declarative 3references to elements on the file system, allowing our system to automatically detect what modes it can support given 4the constraints: e.g. can the file system symlink, can the files be read, executed, etc. 5""" 6from __future__ import absolute_import, unicode_literals 7 8import os 9from abc import ABCMeta, abstractmethod 10from collections import OrderedDict 11from stat import S_IXGRP, S_IXOTH, S_IXUSR 12 13from six import add_metaclass 14 15from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink 16from virtualenv.util.path import copy, make_exe, symlink 17from virtualenv.util.six import ensure_text 18 19 20class RefMust(object): 21 NA = "NA" 22 COPY = "copy" 23 SYMLINK = "symlink" 24 25 26class RefWhen(object): 27 ANY = "ANY" 28 COPY = "copy" 29 SYMLINK = "symlink" 30 31 32@add_metaclass(ABCMeta) 33class PathRef(object): 34 """Base class that checks if a file reference can be symlink/copied""" 35 36 FS_SUPPORTS_SYMLINK = fs_supports_symlink() 37 FS_CASE_SENSITIVE = fs_is_case_sensitive() 38 39 def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): 40 self.must = must 41 self.when = when 42 self.src = src 43 try: 44 self.exists = src.exists() 45 except OSError: 46 self.exists = False 47 self._can_read = None if self.exists else False 48 self._can_copy = None if self.exists else False 49 self._can_symlink = None if self.exists else False 50 51 def __repr__(self): 52 return "{}(src={})".format(self.__class__.__name__, self.src) 53 54 @property 55 def can_read(self): 56 if self._can_read is None: 57 if self.src.is_file(): 58 try: 59 with self.src.open("rb"): 60 self._can_read = True 61 except OSError: 62 self._can_read = False 63 else: 64 self._can_read = os.access(ensure_text(str(self.src)), os.R_OK) 65 return self._can_read 66 67 @property 68 def can_copy(self): 69 if self._can_copy is None: 70 if self.must == RefMust.SYMLINK: 71 self._can_copy = self.can_symlink 72 else: 73 self._can_copy = self.can_read 74 return self._can_copy 75 76 @property 77 def can_symlink(self): 78 if self._can_symlink is None: 79 if self.must == RefMust.COPY: 80 self._can_symlink = self.can_copy 81 else: 82 self._can_symlink = self.FS_SUPPORTS_SYMLINK and self.can_read 83 return self._can_symlink 84 85 @abstractmethod 86 def run(self, creator, symlinks): 87 raise NotImplementedError 88 89 def method(self, symlinks): 90 if self.must == RefMust.SYMLINK: 91 return symlink 92 if self.must == RefMust.COPY: 93 return copy 94 return symlink if symlinks else copy 95 96 97@add_metaclass(ABCMeta) 98class ExePathRef(PathRef): 99 """Base class that checks if a executable can be references via symlink/copy""" 100 101 def __init__(self, src, must=RefMust.NA, when=RefWhen.ANY): 102 super(ExePathRef, self).__init__(src, must, when) 103 self._can_run = None 104 105 @property 106 def can_symlink(self): 107 if self.FS_SUPPORTS_SYMLINK: 108 return self.can_run 109 return False 110 111 @property 112 def can_run(self): 113 if self._can_run is None: 114 mode = self.src.stat().st_mode 115 for key in [S_IXUSR, S_IXGRP, S_IXOTH]: 116 if mode & key: 117 self._can_run = True 118 break 119 else: 120 self._can_run = False 121 return self._can_run 122 123 124class PathRefToDest(PathRef): 125 """Link a path on the file system""" 126 127 def __init__(self, src, dest, must=RefMust.NA, when=RefWhen.ANY): 128 super(PathRefToDest, self).__init__(src, must, when) 129 self.dest = dest 130 131 def run(self, creator, symlinks): 132 dest = self.dest(creator, self.src) 133 method = self.method(symlinks) 134 dest_iterable = dest if isinstance(dest, list) else (dest,) 135 if not dest.parent.exists(): 136 dest.parent.mkdir(parents=True, exist_ok=True) 137 for dst in dest_iterable: 138 method(self.src, dst) 139 140 141class ExePathRefToDest(PathRefToDest, ExePathRef): 142 """Link a exe path on the file system""" 143 144 def __init__(self, src, targets, dest, must=RefMust.NA, when=RefWhen.ANY): 145 ExePathRef.__init__(self, src, must, when) 146 PathRefToDest.__init__(self, src, dest, must, when) 147 if not self.FS_CASE_SENSITIVE: 148 targets = list(OrderedDict((i.lower(), None) for i in targets).keys()) 149 self.base = targets[0] 150 self.aliases = targets[1:] 151 self.dest = dest 152 153 def run(self, creator, symlinks): 154 bin_dir = self.dest(creator, self.src).parent 155 dest = bin_dir / self.base 156 method = self.method(symlinks) 157 method(self.src, dest) 158 if not symlinks: 159 make_exe(dest) 160 for extra in self.aliases: 161 link_file = bin_dir / extra 162 if link_file.exists(): 163 link_file.unlink() 164 if symlinks: 165 link_file.symlink_to(self.base) 166 else: 167 copy(self.src, link_file) 168 if not symlinks: 169 make_exe(link_file) 170 171 def __repr__(self): 172 return "{}(src={}, alias={})".format(self.__class__.__name__, self.src, self.aliases) 173