[PyKDE] Building SIP with distutils

Giovanni Bajo rasky at develer.com
Fri Dec 16 17:36:52 GMT 2005


Hello,

I'm working so that people wanting to use SIP as a binding generator do not
have to play with the custom build engine (which is required only by PyQt).
I already contributed sipdistutils.py which lets compile SIP-generated
bindings with distutils.

The next step is to compile SIP itself with distutils, and this is what the
attached setup.py does. It's almost complete:

- It builds and installs SIP correctly. Compared to the custom build system,
the main differences is that the sip executable is put into the python
scripts directory (by default: /usr/bin under Linux,  c:\python\scripts
under Windows), and the sip header file is put within the per-package python
include directory (by default: /usr/include/python2.4/sip/sip.h under Linux,
c:\python\include\sip\sip.h under Windows).

- Of course, we now have all the fancy distutils options, so "setup.py
bdist_wininst" will generate "sip-snapshot-20051130.win32-py2.4.exe" as
expected, and "setup.py bdist_rpm" will generate
"sip-snapshot_20051130-1.i386.rpm" and "sip-snapshot_20051130-1.src.rpm",
and they all work correctly:

$ rpm -qpl sip-snapshot_20051130-1.i386.rpm
/usr/bin/sip
/usr/include/python2.4/sip/sip.h
/usr/lib/python2.4/site-packages/sip.so
/usr/lib/python2.4/site-packages/sipdistutils.py
/usr/lib/python2.4/site-packages/sipdistutils.pyc

- I had to work around a couple of issues in distutils, and to add support
for building native scripts (that is, executables). Not too bad though, as
the compiler abstraction class in distutils already had a "link_executable"
method, it's just that it was not hooked up everywhere as needed.

- It's incomplete, as in it still does not generate sipconfig.py, but that's
easy to add. It's the last step though, I wanted to make sure that
everything else is OK.

Phil, I'd appreciate if you (or others) could give this a go and confirm
that it mostly works. I'll add support for generation of sipconfig.py in the
next few days.
-- 
Giovanni Bajo
-------------- next part --------------
#!/usr/bin/env python
from distutils.core import setup, Extension
from distutils.command.build_ext import build_ext
from distutils.command.build import build
from distutils.command.sdist import sdist
from distutils.command.bdist_rpm import bdist_rpm
from distutils.command.install import install
from distutils.command.install_scripts import install_scripts
from distutils.dist import Distribution
import sys, os
import glob

sip_version = 0x040300
sip_version_str = "snapshot-20051130"

desc = "SIP - Python C/C++ Bindings Generator"

long_desc = """\
SIP is a tool for automatically generating Python bindings for C and C++
libraries. Bindings are fast to load, have minimum memory consumption. SIP
features automatic conversion between standard Python and C/C++ data types,
supports function and methods overloading, allows to derive Python classes
from C++ classes, (re)implementation virtual methods, C++ exceptions (and
their conversion into Python exceptions), explicit ownership semantic of
objects, and full support for the Python cyclic garbage collector."""

class _Distribution(Distribution):
    def __init__(self, *args, **kwargs):
        self.native_scripts = []
        Distribution.__init__(self, *args, **kwargs)

    def has_native_scripts(self):
        return len(self.native_scripts) > 0

    def is_pure(self):
        return Distribution.is_pure(self) and not self.has_native_scripts()

# Redefine the build class so that "setup.py build" will also run "build_native_scripts"
class _build(build):
    def has_native_scripts(self):
        return self.distribution.has_native_scripts()

    sub_commands = build.sub_commands + [
        ('build_native_scripts', has_native_scripts)
        ]

# Redefine install so that install_scripts will be run even if there are
# native scripts.
class _install(install):
    def has_scripts(self):
        return install.has_scripts(self) or self.distribution.has_native_scripts()

    sub_commands = install.sub_commands[:]
    for i, (name, cond) in enumerate(sub_commands):
        if name == "install_scripts":
            sub_commands[i] = (name, has_scripts)


# Redefine the install_scripts class so that "setup.py build_native_scripts" will
# be automatically run.
class _install_scripts(install_scripts):
    def run(self, *args, **kwargs):
        if not self.skip_build:
            self.run_command('build_native_scripts')
        install_scripts.run(self, *args, **kwargs)

# Redefine sdist so that it includes native script source files by default.
# Also add dependencies for both native scripts and extensions (this is
# a distutil missing feature already added to setuptools).
class _sdist(sdist):
    def __get_depends(self, b):
        deps = []
        for e in b.extensions:
            if e.depends:
                deps.extend(e.depends)
        return deps

    def add_defaults(self, *args, **kwargs):
        sdist.add_defaults(self, *args, **kwargs)

        if self.distribution.has_native_scripts():
            build_native_scripts = self.get_finalized_command('build_native_scripts')
            self.filelist.extend(build_native_scripts.get_source_files())
            self.filelist.extend(self.__get_depends(build_native_scripts))

        if self.distribution.has_ext_modules():
            build_ext = self.get_finalized_command('build_ext')
            self.filelist.extend(self.__get_depends(build_ext))


# Redefine bdist_rpm to fix a bug when the version number contains hypens ("-").
# This is a bug in disutils shipped with Python 2.4.2, which I reported to
# the distutils-sig mailing list.
class _bdist_rpm(bdist_rpm):
    def run_command(self, cmd, *args, **kwargs):
        if cmd == "sdist":
            old_version = self.distribution.metadata.version
            if self.distribution.metadata.version is not None:
                self.distribution.metadata.version = old_version.replace("-", "_")
            print self.distribution.get_version()
            bdist_rpm.run_command(self, cmd, *args, **kwargs)
            self.distribution.metadata.version = old_version
            return

        bdist_rpm.run_command(self, cmd, *args, **kwargs)

class build_native_scripts(build_ext):
    description = "Compile scripts in the form of native executables"

    user_options = build_ext.user_options[:]
    for i,opt in enumerate(user_options):
        if opt[0] == "build-lib=":
            del user_options[i]
            break
    else:
        assert 0, "build-lib not found?"

    user_options = [
        ('build-lib=', 'b',
         "directory for compiled scripts"),
    ] + user_options

    def finalize_options(self, *args, **kwargs):
        self.set_undefined_options('build',
                                   ('build_scripts', 'build_lib'))

        build_ext.finalize_options(self, *args, **kwargs)

        # We must build things marked as native scripts
        self.extensions = self.distribution.native_scripts

    def build_extension(self, *args, **kwargs):
        # piggy-back the executable linking function
        def fake_link_shared_object(*args, **kwargs):
            del kwargs["export_symbols"]
            del kwargs["build_temp"]
            return self.compiler.link_executable(*args, **kwargs)

        self.compiler.link_shared_object = fake_link_shared_object
        build_ext.build_extension(self, *args, **kwargs)

    def get_ext_filename(self, *args, **kwargs):
        from distutils.sysconfig import get_config_var
        fn = build_ext.get_ext_filename(self, *args, **kwargs)
        # Remove .pyd, and no need to put an extension, as link_executable
        # will automatically take care of it.
        return os.path.splitext(fn)[0]

setup(
    name = 'sip',
    version = sip_version_str,
    description = desc,
    long_description = long_desc,
    author = "Riverbank Computing Ltd.",
    author_email = "info at riverbankcomputing.co.uk",
    url = "http://www.riverbankcomputing.co.uk/sip/",
    download_url = "http://www.riverbankcomputing.co.uk/sip/download.php",
    license = "Python (MIT style)",
    platforms = "Python 2.3 and later.",
    classifiers = [
        "Development Status :: 5 - Production/Stable",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: Python Software Foundation License",
        "Operating System :: MacOS :: MacOS X",
        "Operating System :: Microsoft :: Windows",
        "Operating System :: POSIX",
        "Programming Language :: Python",
        "Programming Language :: C"
        "Programming Language :: C++",
        "Topic :: Software Development :: Code Generators",
        "Topic :: Software Development :: Libraries :: Python Modules",
        ],

    py_modules=[
        "sipdistutils",
        ],
    ext_modules=[
        Extension("sip",
                  glob.glob(os.path.join("siplib", "*.c")) +
                  glob.glob(os.path.join("siplib", "*.cpp")),
                  include_dirs=["."],
                  depends=glob.glob(os.path.join("siplib", "*.h"))),
        ],
    native_scripts=[
        Extension("sip",
                  glob.glob(os.path.join("sipgen", "*.c")) +
                  glob.glob(os.path.join("sipgen", "*.cpp")),
                  include_dirs=["."],
                  depends=glob.glob(os.path.join("sipgen", "*.h"))),
        ],
    headers = [ "sipgen/sip.h" ],

    distclass = _Distribution,
    cmdclass = {
        'build': _build,
        'build_native_scripts': build_native_scripts,
        'install': _install,
        'install_scripts': _install_scripts,
        'sdist': _sdist,
        'bdist_rpm': _bdist_rpm,
        },
)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MANIFEST.in
Type: application/octet-stream
Size: 194 bytes
Desc: not available
Url : http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20051216/d6be01ff/MANIFEST.obj


More information about the PyQt mailing list