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