deployment.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 # Copyright (c) 2013 - 2014 Spotify AB
00003 
00004 # This file is part of dh-virtualenv.
00005 
00006 # dh-virtualenv is free software: you can redistribute it and/or
00007 # modify it under the terms of the GNU General Public License as
00008 # published by the Free Software Foundation, either version 2 of the
00009 # License, or (at your option) any later version.
00010 
00011 # dh-virtualenv is distributed in the hope that it will be useful, but
00012 # WITHOUT ANY WARRANTY; without even the implied warranty of
00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00014 # General Public License for more details.
00015 
00016 # You should have received a copy of the GNU General Public License
00017 # along with dh-virtualenv. If not, see
00018 # <http://www.gnu.org/licenses/>.
00019 
00020 # Note for catkin_virtualenv users: local changes from upstream are marked with (pbovbel)
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 # (pbovbel) Log subprocess calls
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,  # (pbovbel): addition
00062                  pip_version=None,  # (pbovbel): addition
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         # (pbovbel): override the debian_root directory to allow a custom install path
00071         # self.debian_root = os.path.join(
00072         #     'debian', package, install_root.lstrip('/'))
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         # We need to prefix the pip run with the location of python
00099         # executable. Otherwise it would just blow up due to too long
00100         # shebang-line.
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         # (pbovbel): make logging optional, since --log overrides -q flag for pip
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         # Keep a copy with well-suported options only (for upgrading pip itself)
00125         self.pip_upgrade_args = self.pip_args[:]
00126 
00127         # Add in any user supplied pip args
00128         self.pip_args.extend(extra_pip_arg)
00129 
00130         # (pbovbel) Set pip_upgrade_args here to keep flags like -q, disregard L111 above.
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         # Add in any user supplied pip args
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         # Install preinstall stage packages. This is handy if you need
00200         # a custom package to install dependencies (think something
00201         # along lines of setuptools), but that does not get installed
00202         # by default virtualenv.
00203         if self.upgrade_pip:
00204             # (pbovbel) allow pinning the pip version
00205             pip_package = 'pip==' + self.pip_version if self.pip_version else 'pip'
00206             # First, bootstrap pip with a reduced option set (well-supported options)
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         # The virtualenv might end up with a local folder that points outside the package
00283         # Specifically it might point at the build environment that created it!
00284         # Make those links relative
00285         # See https://github.com/pypa/virtualenv/commit/5cb7cd652953441a6696c15bdac3c4f9746dfaa1
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             # "local" points directly to its containing directory
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                 # If the symlink is already relative, we don't
00303                 # want to touch it.
00304                 continue
00305 
00306             new_target = os.path.relpath(existing_target, local_dir)
00307             os.unlink(path)
00308             os.symlink(new_target, path)


catkin_virtualenv
Author(s): Paul Bovbel
autogenerated on Thu Jun 27 2019 20:04:50