Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/virtualenv/create/creator.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:d30785e31577 | 1:56ad4e20f292 |
---|---|
1 from __future__ import absolute_import, print_function, unicode_literals | |
2 | |
3 import json | |
4 import logging | |
5 import os | |
6 import sys | |
7 from abc import ABCMeta, abstractmethod | |
8 from argparse import ArgumentTypeError | |
9 from ast import literal_eval | |
10 from collections import OrderedDict | |
11 from textwrap import dedent | |
12 | |
13 from six import add_metaclass | |
14 | |
15 from virtualenv.discovery.cached_py_info import LogCmd | |
16 from virtualenv.info import WIN_CPYTHON_2 | |
17 from virtualenv.util.path import Path, safe_delete | |
18 from virtualenv.util.six import ensure_str, ensure_text | |
19 from virtualenv.util.subprocess import run_cmd | |
20 from virtualenv.version import __version__ | |
21 | |
22 from .pyenv_cfg import PyEnvCfg | |
23 | |
24 HERE = Path(os.path.abspath(__file__)).parent | |
25 DEBUG_SCRIPT = HERE / "debug.py" | |
26 | |
27 | |
28 class CreatorMeta(object): | |
29 def __init__(self): | |
30 self.error = None | |
31 | |
32 | |
33 @add_metaclass(ABCMeta) | |
34 class Creator(object): | |
35 """A class that given a python Interpreter creates a virtual environment""" | |
36 | |
37 def __init__(self, options, interpreter): | |
38 """Construct a new virtual environment creator. | |
39 | |
40 :param options: the CLI option as parsed from :meth:`add_parser_arguments` | |
41 :param interpreter: the interpreter to create virtual environment from | |
42 """ | |
43 self.interpreter = interpreter | |
44 self._debug = None | |
45 self.dest = Path(options.dest) | |
46 self.clear = options.clear | |
47 self.pyenv_cfg = PyEnvCfg.from_folder(self.dest) | |
48 self.app_data = options.app_data | |
49 | |
50 def __repr__(self): | |
51 return ensure_str(self.__unicode__()) | |
52 | |
53 def __unicode__(self): | |
54 return "{}({})".format(self.__class__.__name__, ", ".join("{}={}".format(k, v) for k, v in self._args())) | |
55 | |
56 def _args(self): | |
57 return [ | |
58 ("dest", ensure_text(str(self.dest))), | |
59 ("clear", self.clear), | |
60 ] | |
61 | |
62 @classmethod | |
63 def can_create(cls, interpreter): | |
64 """Determine if we can create a virtual environment. | |
65 | |
66 :param interpreter: the interpreter in question | |
67 :return: ``None`` if we can't create, any other object otherwise that will be forwarded to \ | |
68 :meth:`add_parser_arguments` | |
69 """ | |
70 return True | |
71 | |
72 @classmethod | |
73 def add_parser_arguments(cls, parser, interpreter, meta, app_data): | |
74 """Add CLI arguments for the creator. | |
75 | |
76 :param parser: the CLI parser | |
77 :param app_data: the application data folder | |
78 :param interpreter: the interpreter we're asked to create virtual environment for | |
79 :param meta: value as returned by :meth:`can_create` | |
80 """ | |
81 parser.add_argument( | |
82 "dest", help="directory to create virtualenv at", type=cls.validate_dest, | |
83 ) | |
84 parser.add_argument( | |
85 "--clear", | |
86 dest="clear", | |
87 action="store_true", | |
88 help="remove the destination directory if exist before starting (will overwrite files otherwise)", | |
89 default=False, | |
90 ) | |
91 | |
92 @abstractmethod | |
93 def create(self): | |
94 """Perform the virtual environment creation.""" | |
95 raise NotImplementedError | |
96 | |
97 @classmethod | |
98 def validate_dest(cls, raw_value): | |
99 """No path separator in the path, valid chars and must be write-able""" | |
100 | |
101 def non_write_able(dest, value): | |
102 common = Path(*os.path.commonprefix([value.parts, dest.parts])) | |
103 raise ArgumentTypeError( | |
104 "the destination {} is not write-able at {}".format(dest.relative_to(common), common), | |
105 ) | |
106 | |
107 # the file system must be able to encode | |
108 # note in newer CPython this is always utf-8 https://www.python.org/dev/peps/pep-0529/ | |
109 encoding = sys.getfilesystemencoding() | |
110 refused = OrderedDict() | |
111 kwargs = {"errors": "ignore"} if encoding != "mbcs" else {} | |
112 for char in ensure_text(raw_value): | |
113 try: | |
114 trip = char.encode(encoding, **kwargs).decode(encoding) | |
115 if trip == char: | |
116 continue | |
117 raise ValueError(trip) | |
118 except ValueError: | |
119 refused[char] = None | |
120 if refused: | |
121 raise ArgumentTypeError( | |
122 "the file system codec ({}) cannot handle characters {!r} within {!r}".format( | |
123 encoding, "".join(refused.keys()), raw_value, | |
124 ), | |
125 ) | |
126 if os.pathsep in raw_value: | |
127 raise ArgumentTypeError( | |
128 "destination {!r} must not contain the path separator ({}) as this would break " | |
129 "the activation scripts".format(raw_value, os.pathsep), | |
130 ) | |
131 | |
132 value = Path(raw_value) | |
133 if value.exists() and value.is_file(): | |
134 raise ArgumentTypeError("the destination {} already exists and is a file".format(value)) | |
135 if (3, 3) <= sys.version_info <= (3, 6): | |
136 # pre 3.6 resolve is always strict, aka must exists, sidestep by using os.path operation | |
137 dest = Path(os.path.realpath(raw_value)) | |
138 else: | |
139 dest = Path(os.path.abspath(str(value))).resolve() # on Windows absolute does not imply resolve so use both | |
140 value = dest | |
141 while dest: | |
142 if dest.exists(): | |
143 if os.access(ensure_text(str(dest)), os.W_OK): | |
144 break | |
145 else: | |
146 non_write_able(dest, value) | |
147 base, _ = dest.parent, dest.name | |
148 if base == dest: | |
149 non_write_able(dest, value) # pragma: no cover | |
150 dest = base | |
151 return str(value) | |
152 | |
153 def run(self): | |
154 if self.dest.exists() and self.clear: | |
155 logging.debug("delete %s", self.dest) | |
156 safe_delete(self.dest) | |
157 self.create() | |
158 self.set_pyenv_cfg() | |
159 self.setup_ignore_vcs() | |
160 | |
161 def set_pyenv_cfg(self): | |
162 self.pyenv_cfg.content = OrderedDict() | |
163 self.pyenv_cfg["home"] = self.interpreter.system_exec_prefix | |
164 self.pyenv_cfg["implementation"] = self.interpreter.implementation | |
165 self.pyenv_cfg["version_info"] = ".".join(str(i) for i in self.interpreter.version_info) | |
166 self.pyenv_cfg["virtualenv"] = __version__ | |
167 | |
168 def setup_ignore_vcs(self): | |
169 """Generate ignore instructions for version control systems.""" | |
170 # mark this folder to be ignored by VCS, handle https://www.python.org/dev/peps/pep-0610/#registered-vcs | |
171 git_ignore = self.dest / ".gitignore" | |
172 if not git_ignore.exists(): | |
173 git_ignore.write_text( | |
174 dedent( | |
175 """ | |
176 # created by virtualenv automatically | |
177 * | |
178 """, | |
179 ).lstrip(), | |
180 ) | |
181 # Mercurial - does not support the .hgignore file inside a subdirectory directly, but only if included via the | |
182 # subinclude directive from root, at which point on might as well ignore the directory itself, see | |
183 # https://www.selenic.com/mercurial/hgignore.5.html for more details | |
184 # Bazaar - does not support ignore files in sub-directories, only at root level via .bzrignore | |
185 # Subversion - does not support ignore files, requires direct manipulation with the svn tool | |
186 | |
187 @property | |
188 def debug(self): | |
189 """ | |
190 :return: debug information about the virtual environment (only valid after :meth:`create` has run) | |
191 """ | |
192 if self._debug is None and self.exe is not None: | |
193 self._debug = get_env_debug_info(self.exe, self.debug_script(), self.app_data) | |
194 return self._debug | |
195 | |
196 # noinspection PyMethodMayBeStatic | |
197 def debug_script(self): | |
198 return DEBUG_SCRIPT | |
199 | |
200 | |
201 def get_env_debug_info(env_exe, debug_script, app_data): | |
202 env = os.environ.copy() | |
203 env.pop(str("PYTHONPATH"), None) | |
204 | |
205 with app_data.ensure_extracted(debug_script) as debug_script: | |
206 cmd = [str(env_exe), str(debug_script)] | |
207 if WIN_CPYTHON_2: | |
208 cmd = [ensure_text(i) for i in cmd] | |
209 logging.debug(str("debug via %r"), LogCmd(cmd)) | |
210 code, out, err = run_cmd(cmd) | |
211 | |
212 # noinspection PyBroadException | |
213 try: | |
214 if code != 0: | |
215 result = literal_eval(out) | |
216 else: | |
217 result = json.loads(out) | |
218 if err: | |
219 result["err"] = err | |
220 except Exception as exception: | |
221 return {"out": out, "err": err, "returncode": code, "exception": repr(exception)} | |
222 if "sys" in result and "path" in result["sys"]: | |
223 del result["sys"]["path"][0] | |
224 return result |