20 from __future__
import print_function
30 from urllib.request
import urlretrieve
33 from urllib
import urlretrieve
35 from distutils.spawn
import find_executable
37 from .
import run_command, relocate
38 from .collect_requirements
import collect_requirements
40 _BYTECODE_REGEX = re.compile(
r".*\.py[co]")
41 _COMMENT_REGEX = re.compile(
r"(^|\s+)#.*$", flags=re.MULTILINE)
43 logger = logging.getLogger(__name__)
48 """ Manage a virtualenv at the specified path. """ 51 def initialize(self, python, use_system_packages, extra_pip_args, clean=True):
52 """ Initialize a new virtualenv using the specified python version and extra arguments. """ 55 shutil.rmtree(self.
path)
59 system_python = find_executable(python)
62 error_msg =
"Unable to find a system-installed {}.".format(python)
63 if python
and python[0].isdigit():
64 error_msg +=
" Perhaps you meant python{}".format(python)
65 raise RuntimeError(error_msg)
74 virtualenv = [system_python,
"-m",
"venv"]
76 virtualenv = [
"virtualenv",
"--no-setuptools",
"--verbose",
"--python", python]
79 preinstall += [
"setuptools>=44,<45"]
81 if use_system_packages:
82 virtualenv.append(
"--system-site-packages")
84 without_pip = self.
_check_module(system_python,
"ensurepip")
is False 86 virtualenv.append(
'--without-pip')
88 virtualenv.append(self.
path)
94 [
'python',
"-cimport sys; print('{}.{}'.format(*sys.version_info))"],
96 version = version_proc.stdout
97 if isinstance(version, bytes):
98 version = version.decode(
'utf-8')
99 version = version.strip()
101 get_pip_path, _ = urlretrieve(
"https://bootstrap.pypa.io/pip/get-pip.py")
104 run_command([self.
_venv_bin(
"python"),
"-m",
"pip",
"install"] + extra_pip_args + preinstall, check=
True)
106 def install(self, requirements, extra_pip_args):
107 """ Sync a virtualenv with the specified requirements. """ 108 command = [self.
_venv_bin(
"python"),
"-m",
"pip",
"install"] + extra_pip_args
109 for req
in requirements:
112 def check(self, requirements, extra_pip_args):
113 """ Check if a set of requirements is completely locked. """ 114 with open(requirements,
"r") as f: 115 existing_requirements = f.read() 118 command = [self.
_venv_bin(
"pip-compile"),
"--no-header",
"--annotation-style",
"line", requirements,
"-o",
"-"]
120 command += [
"--pip-args",
" ".join(extra_pip_args)]
122 generated_requirements =
run_command(command, check=
True, capture_output=
True).stdout.decode()
124 def _format(content):
126 content = _COMMENT_REGEX.sub(
"", content)
128 content = content.lower()
130 content = content.splitlines()
136 diff = list(difflib.unified_diff(_format(existing_requirements), _format(generated_requirements)))
140 def lock(self, package_name, input_requirements, no_overwrite, extra_pip_args):
141 """ Create a frozen requirement set from a set of input specifications. """ 145 logger.info(
"Package doesn't export any requirements, step can be skipped")
148 if no_overwrite
and os.path.exists(output_requirements):
149 logger.info(
"Lock file already exists, not overwriting")
152 pip_compile = self.
_venv_bin(
"pip-compile")
153 command = [pip_compile,
"--no-header",
"--annotation-style",
"line", input_requirements]
155 if os.path.normpath(input_requirements) == os.path.normpath(output_requirements):
157 "Trying to write locked requirements {} into a path specified as input: {}".format(
158 output_requirements, input_requirements
163 command += [
"--pip-args",
" ".join(extra_pip_args)]
165 command += [
"-o", output_requirements]
169 logger.info(
"Wrote new lock file to {}".format(output_requirements))
172 """ Relocate a virtualenv to another directory. """ 174 relocate.fix_shebangs(self.
path, target_dir)
175 relocate.fix_activate_path(self.
path, target_dir)
179 local_dir = os.path.join(self.
path,
"local")
180 if os.path.exists(local_dir):
181 shutil.rmtree(local_dir)
184 if os.path.exists(os.path.join(self.
path,
"bin", binary_name)):
185 return os.path.abspath(os.path.join(self.
path,
"bin", binary_name))
186 elif os.path.exists(os.path.join(self.
path,
"local",
"bin", binary_name)):
187 return os.path.abspath(os.path.join(self.
path,
"local",
"bin", binary_name))
188 raise RuntimeError(
"Binary {} not found in venv".format(binary_name))
192 with open(os.devnull,
"w")
as devnull:
194 run_command([python_executable,
"-cimport {}".format(module)], stderr=devnull, check=
True)
196 except subprocess.CalledProcessError:
200 """ Remove all .py[co] files since they embed absolute paths. """ 201 for root, _, files
in os.walk(self.
path):
203 if _BYTECODE_REGEX.match(f):
204 os.remove(os.path.join(root, f))
def collect_requirements(package_name, no_deps=False)
def check(self, requirements, extra_pip_args)
def initialize(self, python, use_system_packages, extra_pip_args, clean=True)
def lock(self, package_name, input_requirements, no_overwrite, extra_pip_args)
def install(self, requirements, extra_pip_args)
def _check_module(self, python_executable, module)
def relocate(self, target_dir)
def _venv_bin(self, binary_name)
def _delete_bytecode(self)
def run_command(cmd, args, kwargs)