00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 from __future__ import print_function
00022
00023
00024 import os
00025 import re
00026 import shutil
00027 import subprocess
00028 import tempfile
00029
00030 ROOT_ENV_KEY = 'DH_VIRTUALENV_INSTALL_ROOT'
00031 DEFAULT_INSTALL_DIR = '/opt/venvs/'
00032 PYTHON_INTERPRETERS = ['python', 'pypy', 'ipy', 'jython']
00033 _PYTHON_INTERPRETERS_REGEX = r'\(' + r'\|'.join(PYTHON_INTERPRETERS) + r'\)'
00034
00035
00036
00037 def check_call(cmd, *args, **kwargs):
00038 print(' '.join(cmd))
00039 return subprocess.check_call(cmd, *args, **kwargs)
00040
00041
00042 class Deployment(object):
00043 def __init__(self,
00044 package,
00045 extra_urls=[],
00046 preinstall=[],
00047 pip_tool='pip',
00048 upgrade_pip=False,
00049 index_url=None,
00050 setuptools=False,
00051 python=None,
00052 builtin_venv=False,
00053 builtin_pip=False,
00054 sourcedirectory=None,
00055 verbose=False,
00056 extra_pip_arg=[],
00057 extra_virtualenv_arg=[],
00058 use_system_packages=False,
00059 skip_install=False,
00060 install_suffix=None,
00061 log_file=tempfile.NamedTemporaryFile().name,
00062 pip_version=None,
00063 requirements_filename='requirements.txt'):
00064
00065 self.package = package
00066
00067 install_root = os.environ.get(ROOT_ENV_KEY, DEFAULT_INSTALL_DIR)
00068 self.install_suffix = install_suffix
00069
00070
00071
00072
00073 self.debian_root = '.'
00074
00075 if install_suffix is None:
00076 self.virtualenv_install_dir = os.path.join(install_root, self.package)
00077 self.package_dir = os.path.join(self.debian_root, package)
00078 else:
00079 self.virtualenv_install_dir = os.path.join(install_root, install_suffix)
00080 self.package_dir = os.path.join(self.debian_root, install_suffix)
00081
00082 self.bin_dir = os.path.join(self.package_dir, 'bin')
00083 self.local_bin_dir = os.path.join(self.package_dir, 'local', 'bin')
00084
00085 self.preinstall = preinstall
00086 self.upgrade_pip = upgrade_pip
00087 self.pip_version = pip_version
00088 self.extra_virtualenv_arg = extra_virtualenv_arg
00089 self.verbose = verbose
00090 self.setuptools = setuptools
00091 self.python = python
00092 self.builtin_venv = builtin_venv
00093 self.sourcedirectory = '.' if sourcedirectory is None else sourcedirectory
00094 self.use_system_packages = use_system_packages
00095 self.skip_install = skip_install
00096 self.requirements_filename = requirements_filename
00097
00098
00099
00100
00101 python = self.venv_bin('python')
00102 if builtin_pip:
00103 self.pip_preinstall_prefix = [python, '-m', 'pip']
00104 self.pip_prefix = [python, '-m', pip_tool]
00105 else:
00106 self.pip_preinstall_prefix = [python, self.venv_bin('pip')]
00107 self.pip_prefix = [python, self.venv_bin(pip_tool)]
00108 self.pip_args = ['install']
00109
00110 if self.verbose:
00111 self.pip_args.append('-v')
00112
00113 if index_url:
00114 self.pip_args.append('--index-url={0}'.format(index_url))
00115 self.pip_args.extend([
00116 '--extra-index-url={0}'.format(url) for url in extra_urls
00117 ])
00118
00119
00120 self.log_file = log_file
00121 if self.log_file:
00122 self.pip_args.append('--log={0}'.format(os.path.abspath(self.log_file)))
00123
00124
00125 self.pip_upgrade_args = self.pip_args[:]
00126
00127
00128 self.pip_args.extend(extra_pip_arg)
00129
00130
00131 self.pip_upgrade_args = self.pip_args
00132
00133 @classmethod
00134 def from_options(cls, package, options):
00135 verbose = options.verbose or os.environ.get('DH_VERBOSE') == '1'
00136 return cls(package,
00137 extra_urls=options.extra_index_url,
00138 preinstall=options.preinstall,
00139 pip_tool=options.pip_tool,
00140 upgrade_pip=options.upgrade_pip,
00141 index_url=options.index_url,
00142 setuptools=options.setuptools,
00143 python=options.python,
00144 builtin_venv=options.builtin_venv,
00145 builtin_pip=options.builtin_pip,
00146 sourcedirectory=options.sourcedirectory,
00147 verbose=verbose,
00148 extra_pip_arg=options.extra_pip_arg,
00149 extra_virtualenv_arg=options.extra_virtualenv_arg,
00150 use_system_packages=options.use_system_packages,
00151 skip_install=options.skip_install,
00152 install_suffix=options.install_suffix,
00153 requirements_filename=options.requirements_filename)
00154
00155 def clean(self):
00156 shutil.rmtree(self.debian_root)
00157
00158 def create_virtualenv(self):
00159 if self.builtin_venv:
00160 virtualenv = [self.python, '-m', 'venv']
00161
00162 if self.use_system_packages:
00163 virtualenv.append('--system-site-packages')
00164
00165 else:
00166 virtualenv = ['virtualenv']
00167
00168 if self.python:
00169 virtualenv.extend(('--python', self.python))
00170
00171 if self.use_system_packages:
00172 virtualenv.append('--system-site-packages')
00173 else:
00174 virtualenv.append('--no-site-packages')
00175
00176 if self.setuptools:
00177 virtualenv.append('--setuptools')
00178
00179 if self.verbose:
00180 virtualenv.append('--verbose')
00181
00182
00183 if self.extra_virtualenv_arg:
00184 virtualenv.extend(self.extra_virtualenv_arg)
00185
00186 virtualenv.append(self.package_dir)
00187 check_call(virtualenv)
00188
00189 def venv_bin(self, binary_name):
00190 return os.path.abspath(os.path.join(self.bin_dir, binary_name))
00191
00192 def pip_preinstall(self, *args):
00193 return self.pip_preinstall_prefix + self.pip_args + list(args)
00194
00195 def pip(self, *args):
00196 return self.pip_prefix + self.pip_args + list(args)
00197
00198 def install_dependencies(self):
00199
00200
00201
00202
00203 if self.upgrade_pip:
00204
00205 pip_package = 'pip==' + self.pip_version if self.pip_version else 'pip'
00206
00207 print(self.pip_preinstall_prefix + self.pip_upgrade_args + ['-U', pip_package])
00208 check_call(self.pip_preinstall_prefix + self.pip_upgrade_args + ['-U', pip_package])
00209 if self.preinstall:
00210 check_call(self.pip_preinstall(*self.preinstall))
00211
00212 requirements_path = os.path.join(self.sourcedirectory, self.requirements_filename)
00213 if os.path.exists(requirements_path):
00214 check_call(self.pip('-r', requirements_path))
00215
00216 def run_tests(self):
00217 python = self.venv_bin('python')
00218 setup_py = os.path.join(self.sourcedirectory, 'setup.py')
00219 if os.path.exists(setup_py):
00220 check_call([python, 'setup.py', 'test'], cwd=self.sourcedirectory)
00221
00222 def find_script_files(self):
00223 """Find list of files containing python shebangs in the bin directory"""
00224 command = ['grep', '-l', '-r',
00225 '-e', r'^#!.*bin/\(env \)\?{0}'.format(_PYTHON_INTERPRETERS_REGEX),
00226 '-e', r"^'''exec.*bin/{0}".format(_PYTHON_INTERPRETERS_REGEX),
00227 self.bin_dir]
00228 grep_proc = subprocess.Popen(command, stdout=subprocess.PIPE)
00229 files, stderr = grep_proc.communicate()
00230 return set(f for f in files.decode('utf-8').strip().split('\n') if f)
00231
00232 def fix_shebangs(self):
00233 """Translate /usr/bin/python and /usr/bin/env python shebang
00234 lines to point to our virtualenv python.
00235 """
00236 pythonpath = os.path.join(self.virtualenv_install_dir, 'bin/python')
00237 for f in self.find_script_files():
00238 regex = (
00239 r's-^#!.*bin/\(env \)\?{names}\"\?-#!{pythonpath}-;'
00240 r"s-^'''exec'.*bin/{names}-'''exec' {pythonpath}-"
00241 ).format(names=_PYTHON_INTERPRETERS_REGEX, pythonpath=re.escape(pythonpath))
00242 check_call(['sed', '-i', regex, f])
00243
00244 def fix_activate_path(self):
00245 """Replace the `VIRTUAL_ENV` path in bin/activate to reflect the
00246 post-install path of the virtualenv.
00247 """
00248 activate_settings = [
00249 [
00250 'VIRTUAL_ENV="{0}"'.format(self.virtualenv_install_dir),
00251 r'^VIRTUAL_ENV=.*$',
00252 "activate"
00253 ],
00254 [
00255 'setenv VIRTUAL_ENV "{0}"'.format(self.virtualenv_install_dir),
00256 r'^setenv VIRTUAL_ENV.*$',
00257 "activate.csh"
00258 ],
00259 [
00260 'set -gx VIRTUAL_ENV "{0}"'.format(self.virtualenv_install_dir),
00261 r'^set -gx VIRTUAL_ENV.*$',
00262 "activate.fish"
00263 ],
00264 ]
00265
00266 for activate_args in activate_settings:
00267 virtualenv_path = activate_args[0]
00268 pattern = re.compile(activate_args[1], flags=re.M)
00269 activate_file = activate_args[2]
00270
00271 with open(self.venv_bin(activate_file), 'r+') as fh:
00272 content = pattern.sub(virtualenv_path, fh.read())
00273 fh.seek(0)
00274 fh.truncate()
00275 fh.write(content)
00276
00277 def install_package(self):
00278 if not self.skip_install:
00279 check_call(self.pip('.'), cwd=os.path.abspath(self.sourcedirectory))
00280
00281 def fix_local_symlinks(self):
00282
00283
00284
00285
00286 local_dir = os.path.join(self.package_dir, "local")
00287 if not os.path.isdir(local_dir):
00288 return
00289 elif os.path.samefile(self.package_dir, local_dir):
00290
00291 os.unlink(local_dir)
00292 os.symlink(".", local_dir)
00293 return
00294
00295 for d in os.listdir(local_dir):
00296 path = os.path.join(local_dir, d)
00297 if not os.path.islink(path):
00298 continue
00299
00300 existing_target = os.readlink(path)
00301 if not os.path.isabs(existing_target):
00302
00303
00304 continue
00305
00306 new_target = os.path.relpath(existing_target, local_dir)
00307 os.unlink(path)
00308 os.symlink(new_target, path)