Source code for kiwi.dist

#
# Kiwi: a Framework and Enhanced Widgets for Python
#
# Copyright (C) 2005-2006 Async Open Source
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
# USA
#
# Author(s): Johan Dahlin <jdahlin@async.com.br>
#

"""Distutils extensions and utilities"""

from distutils.command.clean import clean
from distutils.command.install_data import install_data
from distutils.command.install_lib import install_lib
from distutils.dep_util import newer
from distutils.log import info, warn
from distutils.sysconfig import get_python_lib
import errno
from fnmatch import fnmatch
from shutil import copyfile
import os
import new
import sys

from setuptools import setup as DS_setup

class _VariableExtender:
    def __init__(self, distribution):
        install = distribution.get_command_obj('install')
        name = distribution.get_name()
        prefix = install.prefix
        if not prefix:
            prefix = sys.prefix

        # Remove trailing /
        if prefix[-1] == '/':
            prefix = prefix[:-1]
        self.prefix = prefix

        self.datadir = os.path.join('share', name)
        if self.prefix == '/usr':
            self.sysconfdir = '/etc'
        else:
            self.sysconfdir = os.path.join('etc')

        pylib = get_python_lib()
        pylib = pylib.replace(sys.prefix + '/', '')
        self.libdir = os.path.dirname(os.path.dirname(pylib))

        self.version = distribution.get_version()

    def extend(self, string, relative=False):
        """
        Expand a variable.
        :param string: string to replace.
        :param relative: if True, assume the content of all variables
            to be relative to the prefix.
        """
        for name, var in [('sysconfdir', self.sysconfdir),
                          ('datadir', self.datadir),
                          ('prefix', self.prefix),
                          ('libdir', self.libdir),
                          ('version', self.version)]:
            if not relative and name not in ['prefix', 'version']:
                var = os.path.join(self.prefix, var)
            string = string.replace('$' + name, var)
        return string

[docs]class KiwiInstallLib(install_lib): # Overridable by subclass resources = {} global_resources = {} def _get_template(self): return os.path.join(self.install_dir, self.distribution.get_name(), '__installed__.py') def _get_revision(self): top_dir = os.path.dirname(os.path.abspath(sys.argv[0])) last_revision = os.path.join(top_dir, '.bzr', 'branch', 'last-revision') # If the file does not exist, we may be building a ppa recipe. # This happens because dpkg-source is run with -i -I, and that # causes .bzr files to be removed. if not os.path.exists(last_revision): last_revision = os.path.join(top_dir, 'last-revision') if not os.path.exists(last_revision): return 0 try: fp = open(last_revision) except OSError, e: if e.errno != errno.ENOENT: raise else: return fp.read().split()[0]
[docs] def generate_template(self): if 'bdist_wininst' in sys.argv: prefix = 'sys.prefix' else: install = self.distribution.get_command_obj('install') # Use raw strings so UNC paths which has \\ works prefix = '%s' % install.prefix or sys.prefix filename = self._get_template() self.mkpath(os.path.dirname(filename)) revision = self._get_revision() fp = open(filename, 'w') # Frozen assumes that prefix is .. relative to library.zip fp.write("""# Generated by setup.py do not modify import os import sys if hasattr(sys, 'frozen'): pos = __file__.find('library.zip') prefix = os.path.dirname(__file__[:pos-1]) else: prefix = %r revision = %r """ % (prefix, revision)) self._write_dictionary(fp, 'resources', self.resources) self._write_dictionary(fp, 'global_resources', self.global_resources) fp.close() return filename
def _write_dictionary(self, fp, name, dictionary): fp.write('%s = {}\n' % name) for key, value in dictionary.items(): value = value.replace('/', os.sep) value = self.varext.extend(value) value = value.replace(self.varext.prefix, '$prefix') parts = [] for part in value.split(os.sep): if part == "": part = os.sep if part == '$prefix': part = 'prefix' else: part = '"%s"' % part parts.append(part) fp.write("%s['%s'] = %s\n" % ( name, key, 'os.path.join(%s)' % ', '.join(parts)))
[docs] def get_outputs(self): filename = self._get_template() files = [filename] + self._bytecode_filenames([filename]) return install_lib.get_outputs(self) + files
[docs] def install(self): self.varext = _VariableExtender(self.distribution) return install_lib.install(self) + [self.generate_template()] # Backwards compat
TemplateInstallLib = KiwiInstallLib
[docs]class KiwiInstallData(install_data):
[docs] def run(self): self.varext = _VariableExtender(self.distribution) # Extend variables in all data files data_files = [] for target, files in self.data_files[:]: data_files.append((self.varext.extend(target, True), files)) self.data_files = data_files return install_data.run(self) # This is so ulgy, but its the only way I found out to include bzr # last-revision when building a source with debuild -S -i -I. # XXX FIXME: Figure out a better way to do this.
[docs]class KiwiClean(clean):
[docs] def run(self): retval = clean.run(self) info("Coping revision file") top_dir = os.path.dirname(os.path.abspath(sys.argv[0])) src = os.path.join(top_dir, '.bzr', 'branch', 'last-revision') dest = os.path.join(top_dir, 'last-revision') copyfile(src, dest) return retval
[docs]def get_site_packages_dir(*dirs): """ Gets the relative path of the site-packages directory This is mainly useful for setup.py usage: >>> setup(... data_files=[(get_site_packages_dir('foo'), files..)]) where files is a list of files to be installed in a directory called foo created in your site-packages directory :param dirs: directory names to be appended """ python_version = sys.version_info[:2] libdir = get_python_lib(plat_specific=False, standard_lib=True, prefix='') if python_version < (2, 6): site = 'site-packages' else: site = 'dist-packages' return os.path.join(libdir, site, *dirs)
[docs]def listfiles(*dirs): """ Lists all files in directories and optionally uses basic shell matching, example: >>> listfiles('data', 'glade', '*.glade') ['data/glade/Foo.glade', 'data/glade/Bar.glade', ...] :param dirs: directory parts """ dir, pattern = os.path.split(os.path.join(*dirs)) abspath = os.path.abspath(dir) if not os.path.exists(abspath): # TODO: Print a warning here? return [] return [os.path.join(dir, filename) for filename in os.listdir(abspath) if filename[0] != '.' and fnmatch(filename, pattern)]
[docs]def compile_po_files(domain, dirname='locale'): """ Compiles po files to mo files. Note. this function depends on gettext utilities being installed :param domain: gettext domain :param dirname: base directory :returns: a list of po files """ if os.system('msgfmt 2> /dev/null') not in [1, 256]: warn('msgfmt is missing, not installing translations') return [] data_files = [] for po in listfiles('po', '*.po'): lang = os.path.basename(po[:-3]) mo = os.path.join(dirname, lang, 'LC_MESSAGES', domain + '.mo') if not os.path.exists(mo) or newer(po, mo): directory = os.path.dirname(mo) if not os.path.exists(directory): info("creating %s" % directory) os.makedirs(directory) cmd = 'msgfmt -o %s %s' % (mo, po) info('compiling %s -> %s' % (po, mo)) if os.system(cmd) != 0: raise SystemExit("Error while running msgfmt") dest = os.path.dirname(os.path.join('share', mo)) data_files.append((dest, [mo])) return data_files
[docs]def listpackages(root, exclude=None): """Recursivly list all packages in directory root Optionally exclude can be specified which is a string like foo/bar. :param root: directory :param exclude: optional packages to be skipped """ packages = [] if not os.path.exists(root): raise ValueError("%s does not exists" % (root,)) if not os.path.isdir(root): raise ValueError("%s must be a directory" % (root,)) if os.path.exists(os.path.join(root, '__init__.py')): packages.append(root.replace('/', '.')) for filename in os.listdir(root): full = os.path.join(root, filename) if os.path.isdir(full): packages.extend(listpackages(full)) if exclude: for package in packages[:]: if package.startswith(exclude): packages.remove(package) return packages
[docs]def setup(**kwargs): """ A drop in replacement for distutils.core.setup which integrates nicely with kiwi.environ :attribute resources: :attribute global_resources: :attribute templates: List of templates to install """ resources = {} global_resources = {} templates = [] if 'resources' in kwargs: resources = kwargs.pop('resources') if 'global_resources' in kwargs: global_resources = kwargs.pop('global_resources') if 'templates' in kwargs: templates = kwargs.pop('templates') def run_install(self): name = kwargs.get('name') if name: self.data_files.extend(compile_po_files(name)) KiwiInstallData.run(self) varext = _VariableExtender(self.distribution) for path, files in templates: install = self.distribution.get_command_obj('install') target = os.path.join(install.prefix, path) if install.root: if target[0] == '/': target = target[1:] target = os.path.join(install.root, target) if not os.path.exists(target): info("creating %s" % target) os.makedirs(target) for filename in files: data = open(filename).read() data = varext.extend(data) target_file = os.path.join(target, os.path.basename(filename)) info('installing template %s' % target_file) open(target_file, 'w').write(data) # Copied from gazpacho, needs to be tested #if 'bdist_wininst' in sys.argv: # prefix_code = 'import sys; prefix = sys.prefix' #else: # install = self.distribution.get_command_obj('install') # prefix = install.prefix # prefix_code = 'prefix = r"%s"' % prefix # distutils uses old style classes InstallData = new.classobj('InstallData', (KiwiInstallData,), dict(run=run_install)) InstallLib = new.classobj('InstallLib', (KiwiInstallLib,), dict(resources=resources, global_resources=global_resources)) cmdclass = dict(install_data=InstallData, install_lib=InstallLib, clean=KiwiClean) kwargs.setdefault('cmdclass', cmdclass).update(cmdclass) DS_setup(**kwargs)