#
# 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)