20 from __future__
import print_function
31 from urllib.request
import urlretrieve
34 from urllib
import urlretrieve
36 from distutils.spawn
import find_executable
38 from .
import run_command, relocate
39 from .collect_requirements
import collect_requirements
41 _BYTECODE_REGEX = re.compile(
r".*\.py[co]")
42 _COMMENT_REGEX = re.compile(
r"(^|\s+)#.*$", flags=re.MULTILINE)
44 logger = logging.getLogger(__name__)
49 """Manage a virtualenv at the specified path."""
52 def initialize(self, python, use_system_packages, extra_pip_args, clean=True):
53 """Initialize a new virtualenv using the specified python version and extra arguments."""
56 shutil.rmtree(self.
path)
60 system_python = find_executable(python)
63 error_msg =
"Unable to find a system-installed {}.".format(python)
64 if python
and python[0].isdigit():
65 error_msg +=
" Perhaps you meant python{}".format(python)
66 raise RuntimeError(error_msg)
75 virtualenv = [system_python,
"-m",
"venv"]
77 virtualenv = [
"virtualenv",
"--no-setuptools",
"--verbose",
"--python", python]
80 preinstall += [
"setuptools>=44,<45"]
82 if use_system_packages:
83 virtualenv.append(
"--system-site-packages")
85 without_pip = self.
_check_module(system_python,
"ensurepip")
is False
87 virtualenv.append(
"--without-pip")
89 virtualenv.append(self.
path)
95 [
"python",
"-cimport sys; print('{}.{}'.format(*sys.version_info))"], capture_output=
True
97 version = version_proc.stdout
98 if isinstance(version, bytes):
99 version = version.decode(
"utf-8")
100 version = version.strip()
102 get_pip_path, _ = urlretrieve(
"https://bootstrap.pypa.io/pip/get-pip.py")
107 [self.
_venv_bin(
"python"),
"-m",
"pip",
"install",
"--no-cache-dir",
"-vvv"] + extra_pip_args + preinstall,
111 def install(self, requirements, extra_pip_args):
112 """Purge the cache first before installing."""
113 command = [self.
_venv_bin(
"python"),
"-m",
"pip",
"cache",
"purge"]
114 """ Sync a virtualenv with the specified requirements."""
115 command = [self.
_venv_bin(
"python"),
"-m",
"pip",
"install",
"-vvv",
"--no-cache-dir"] + extra_pip_args
116 for req
in requirements:
119 def check(self, requirements, extra_pip_args):
120 """Check if a set of requirements is completely locked."""
121 with open(requirements,
"r")
as f:
122 existing_requirements = f.read()
125 command = [self.
_venv_bin(
"pip-compile"),
"--no-header",
"--annotation-style",
"line", requirements,
"-o",
"-"]
127 command += [
"--pip-args",
" ".join(extra_pip_args)]
129 generated_requirements =
run_command(command, check=
True, capture_output=
True).stdout.decode()
131 def _format(content):
133 content = _COMMENT_REGEX.sub(
"", content)
135 content = content.lower()
137 content = content.splitlines()
143 diff = list(difflib.unified_diff(_format(existing_requirements), _format(generated_requirements)))
147 def lock(self, package_name, input_requirements, no_overwrite, extra_pip_args):
148 """Create a frozen requirement set from a set of input specifications."""
152 logger.info(
"Package doesn't export any requirements, step can be skipped")
155 if no_overwrite
and os.path.exists(output_requirements):
156 logger.info(
"Lock file already exists, not overwriting")
159 pip_compile = self.
_venv_bin(
"pip-compile")
160 command = [pip_compile,
"--no-header",
"--annotation-style",
"line", input_requirements]
162 if os.path.normpath(input_requirements) == os.path.normpath(output_requirements):
164 "Trying to write locked requirements {} into a path specified as input: {}".format(
165 output_requirements, input_requirements
170 command += [
"--pip-args",
" ".join(extra_pip_args)]
172 command += [
"-o", output_requirements]
176 logger.info(
"Wrote new lock file to {}".format(output_requirements))
179 """Relocate a virtualenv to another directory."""
181 relocate.fix_shebangs(self.
path, target_dir)
182 relocate.fix_activate_path(self.
path, target_dir)
186 local_dir = os.path.join(self.
path,
"local")
187 if os.path.exists(local_dir):
188 shutil.rmtree(local_dir)
191 if os.path.exists(os.path.join(self.
path,
"bin", binary_name)):
192 return os.path.abspath(os.path.join(self.
path,
"bin", binary_name))
193 elif os.path.exists(os.path.join(self.
path,
"local",
"bin", binary_name)):
194 return os.path.abspath(os.path.join(self.
path,
"local",
"bin", binary_name))
195 raise RuntimeError(
"Binary {} not found in venv".format(binary_name))
199 with open(os.devnull,
"w")
as devnull:
201 run_command([python_executable,
"-cimport {}".format(module)], stderr=devnull, check=
True)
203 except subprocess.CalledProcessError:
207 """Remove all .py[co] files since they embed absolute paths."""
208 for root, _, files
in os.walk(self.
path):
210 if _BYTECODE_REGEX.match(f):
211 os.remove(os.path.join(root, f))