Skip to content

Commit

Permalink
First public release
Browse files Browse the repository at this point in the history
  • Loading branch information
Jyrki Pulliainen committed Oct 10, 2013
0 parents commit c4eb511
Show file tree
Hide file tree
Showing 21 changed files with 1,660 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*.py[oc]

# Temp files
*~
~*
.*~
\#*
.#*
*#

# Sphinx build
doc/_build
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# dh-virtualenv

dh-virtualenv is a tool that aims to combine Debian packaging with
self-contained virtualenv based Python deployments.

The idea behind the dh-virtualenv is to be able to combine power of
Debian packaging with the sandboxed nature of virtualenvs. In addition
to this, using virtualenv enables installing requirements via
[Python Package Index](http://pypi.python.org) instead of relying on
the operating system provided Python packages. The only limiting
factor is that you have to run the same Python interpreter as the
operating system.

## Using dh-virtualenv

Using dh-virtualenv is fairly straightforward. First, you need to
define the requirements of your package in `requirements.txt` file, in
[the format defined by pip](http://www.pip-installer.org/en/latest/cookbook.html#requirements-files).

To build a package using dh-virtualenv, you need to add dh-virtualenv
in to your build dependencies and write following `debian/rules` file:

%:
dh $@ --with python-virtualenv

Note that you might need to provide
additional build dependencies too, if your requirements require them.

Once the package is built, you have a virtualenv contained in a Debian
package and upon installation it gets placed, by default, under
`/usr/share/python/<packagename>`.

For more information and usage documentation, check the accompanying
documentation in the `doc` folder.

## How does it work?

To do the packaging, the package extends debhelper's sequence by
providing a new command in sequence, `dh_virtualenv`, which
effectively replaces following commands from the sequence:

* `dh_auto_install`
* `dh_python2`
* `dh_pycentral`
* `dh_pysupport`

In the sequence the dh_virtualenv is inserted right after dh_perl.

## License

Copyright (c) 2013 Spotify AB

dh-virtualenv is licensed under GPL v2 or later. Full license is
available in the `LICENSE` file.
90 changes: 90 additions & 0 deletions bin/dh_virtualenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2013 Spotify AB

# This file is part of dh-virtualenv.

# dh-virtualenv is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 2 of the
# License, or (at your option) any later version.

# dh-virtualenv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with dh-virtualenv. If not, see
# <http://www.gnu.org/licenses/>.

import logging
import os
import sys


from optparse import OptionParser, SUPPRESS_HELP

# The debpython resides here
sys.path.insert(1, '/usr/share/python/')

from debpython.debhelper import DebHelper
from dh_virtualenv import Deployment

logging.basicConfig(format='%(levelname).1s: %(module)s:%(lineno)d: '
'%(message)s')
log = logging.getLogger(__name__)


def main():
usage = '%prog [options]'
parser = OptionParser(usage, version='%prog 0.1')
parser.add_option('-p', '--package', action='append',
help='act on the package named PACKAGE')
parser.add_option('-N', '--no-package', action='append',
help='do not act on the specified package')
parser.add_option('-v', '--verbose', action='store_true',
default=False, help='Turn on verbose mode')
parser.add_option('--extra-index-url', action='append',
help='extra index URL to pass to pip.',
default=[])
parser.add_option('--preinstall', action='append',
help=('package to install before processing '
'requirements.txt.'),
default=[])
parser.add_option('--pypi-url', help='Base URL of the PyPI server')
# Ignore user-specified option bunldes
parser.add_option('-O', help=SUPPRESS_HELP)

options, args = parser.parse_args(
sys.argv[1:] + os.environ.get('DH_OPTIONS', '').split())

verbose = options.verbose or os.environ.get('DH_VERBOSE') == '1'
if verbose:
log.setLevel(logging.DEBUG)

dh = DebHelper(options.package or None)
for package, details in dh.packages.items():
def _info(msg):
log.info('{0}: {1}'.format(package, msg))

_info('Processing package...')

deploy = Deployment(
package,
extra_urls=options.extra_index_url,
preinstall=options.preinstall,
pypi_url=options.pypi_url,
verbose=verbose,
)

_info('Creating virtualenv')
deploy.create_virtualenv()

deploy.install_dependencies()
deploy.install_package()
deploy.fix_shebangs()


if __name__ == '__main__':
sys.exit(main() or 0)
32 changes: 32 additions & 0 deletions debhelper/python_virtualenv.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#! /usr/bin/perl
# debhelper sequence for wrapping packages inside virtualenvs

# Copyright (c) Spotify AB 2013

# This file is part of dh-virtualenv.

# dh-virtualenv is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 2 of the
# License, or (at your option) any later version.

# dh-virtualenv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with dh-virtualenv. If not, see
# <http://www.gnu.org/licenses/>.

use warnings;
use strict;
use Debian::Debhelper::Dh_Lib;

insert_after("dh_perl", "dh_virtualenv");
remove_command("dh_auto_install");
remove_command("dh_python2");
remove_command("dh_pycentral");
remove_command("dh_pysupport");

1
5 changes: 5 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dh-virtualenv (0.5-1) unstable; urgency=low

* Initial public release

-- Jyrki Pulliainen <jyrki@spotify.com> Wed, 09 Oct 2013 17:18:09 +0200
1 change: 1 addition & 0 deletions debian/compat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8
16 changes: 16 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Source: dh-virtualenv
Section: net
Priority: extra
Maintainer: Jyrki Pulliainen <jyrki@spotify.com>
Build-Depends: debhelper (>= 8), python(>= 2.6.6-3~),
python-setuptools, python-sphinx, python-mock
Standards-Version: 3.9.3
X-Python-Version: >= 2.6

Package: dh-virtualenv
Architecture: all
Depends: ${python:Depends}, ${misc:Depends}, python-virtualenv (>= 1.7),
grep
Description: Wrap & build python packages using virtualenv
This package provides a dh sequencer that helps you to deploy your
virtualenv wrapped installation inside a Debian package.
8 changes: 8 additions & 0 deletions debian/copyright
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: dh-virtualenv
Upstream-Contact: Jyrki Pulliainen <jyrki@spotify.com>
Source: http://www.github.com/spotify/dh-virtualenv

Files: *
Copyright: 2013 Spotify AB
License: GPL-2+
1 change: 1 addition & 0 deletions debian/dh-virtualenv.install
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debhelper/python_virtualenv.pm usr/share/perl5/Debian/Debhelper/Sequence
8 changes: 8 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/make -f

%:
dh $@ --with python2

override_dh_installdocs:
python setup.py build_sphinx
dh_installdocs doc/_build/html
116 changes: 116 additions & 0 deletions dh_virtualenv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013 Spotify AB

# This file is part of dh-virtualenv.

# dh-virtualenv is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 2 of the
# License, or (at your option) any later version.

# dh-virtualenv is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with dh-virtualenv. If not, see
# <http://www.gnu.org/licenses/>.

import os
import shutil
import subprocess
import tempfile

ROOT_ENV_KEY = 'DH_VIRTUALENV_INSTALL_ROOT'
DEFAULT_INSTALL_DIR = '/usr/share/python/'


class Deployment(object):
def __init__(self, package, extra_urls=None, preinstall=None,
pypi_url=None, verbose=False):
self.package = package
self.install_root = os.environ.get(ROOT_ENV_KEY, DEFAULT_INSTALL_DIR)
root = self.install_root.lstrip('/')
self.debian_root = os.path.join('debian', package, root)
self.package_dir = os.path.join(self.debian_root, package)
self.bin_dir = os.path.join(self.package_dir, 'bin')

self.extra_urls = extra_urls if extra_urls is not None else []
self.preinstall = preinstall if preinstall is not None else []
self.pypi_url = pypi_url
self.log_file = tempfile.NamedTemporaryFile()
self.verbose = verbose

def clean(self):
shutil.rmtree(self.debian_root)

def create_virtualenv(self):
subprocess.check_call(['virtualenv',
'--no-site-packages',
self.package_dir])

# We need to prefix the pip run with the location of python
# executable. Otherwise it would just blow up due to too long
# shebang-line.
self.pip_prefix = [
os.path.join(self.bin_dir, 'python'),
os.path.join(self.bin_dir, 'pip'),
]
if self.verbose:
self.pip_prefix.append('-v')

self.pip_prefix.append('install')

if self.pypi_url:
self.pip_prefix.append('--pypi-url={0}'.format(self.pypi_url))
self.pip_prefix.extend([
'--extra-index-url={0}'.format(url) for url in self.extra_urls
])
self.pip_prefix.append('--log={0}'.format(self.log_file.name))

def pip(self, *args):
return self.pip_prefix + list(args)

def install_dependencies(self):
# Install preinstall stage packages. This is handy if you need
# a custom package to install dependencies (think something
# along lines of setuptools), but that does not get installed
# by default virtualenv.
if self.preinstall:
subprocess.check_call(self.pip(*self.preinstall))

if os.path.exists('requirements.txt'):
subprocess.check_call(self.pip('-r', 'requirements.txt'))

def fix_shebangs(self):
"""Translate /usr/bin/python and /usr/bin/env python sheband
lines to point to our virtualenv python.
"""
grep_proc = subprocess.Popen(
['grep', '-l', '-r', '-e', r'^#!.*bin/\(env \)\?python',
self.bin_dir],
stdout=subprocess.PIPE
)
files, stderr = grep_proc.communicate()
files = files.strip()
if not files:
return

pythonpath = os.path.join(self.install_root, self.package, 'bin/python')
for f in files.split('\n'):
subprocess.check_call(
['sed', '-i', r's|^#!.*bin/\(env \)\?python|#!{0}|'.format(
pythonpath),
f])

def install_package(self):
temp = tempfile.NamedTemporaryFile()
subprocess.check_call([
os.path.join(self.bin_dir, 'python'),
'setup.py',
'install',
'--record', temp.name,
'--install-headers',
os.path.join(self.package_dir, 'include/site/python2.6'),
])
Loading

0 comments on commit c4eb511

Please sign in to comment.