[PyKDE] [PATCH] Compiling SIP with distutils

Giovanni Bajo rasky at develer.com
Wed Oct 26 15:43:51 BST 2005


Hello,

I have attached sipdistutils.py which gives preliminar support for compiling
SIP extensions with distutils. It is based on Pyrex's distutils support.


When you build an extension, the following happens:

- The list of provided files is searched for files with extension .sip.
- The files are compiled through "sip" (looking for the correct version),
generating the source code in the distutils build temp directory. The SIP
build file (.sbf) is also generated.
- The .sbf file is parsed to extract the "sources" list of generated files
- The files are appended to the list of sources to be compiled to build the
extension.
- Distutils contains normal execution and the .pyd/.so is created.
- In later runs, the .sbf file is used to do date-based dependency check
across the original .sip file.


Example of usage (setup.py file):

#!/usr/bin/env python
from distutils.core import setup
from distutils.extension import Extension
from sipdistutils import build_ext

setup(
  name = 'boolop',
  ext_modules=[
    Extension("boolop",  ["boolop.sip", "boolop.cpp"],
    ],
  cmdclass = {'build_ext': build_ext}  # use sip's build_ext
)


Caveats:

- It doesn't support building an extension with two sip files with the same
basename.
- It should run whichever SIP is installed in the Python distribution which
is executing distutils. It looks for SIP in the exec_prefix directory (under
Windows, this is the base directory of Python). Otherwise, it defaults to
using the path, but if you have multiple Python/SIP versions that could be
incorrect. I could run a version check between the output of "sip -V" and
sip.SIP_VERSION_STR, but it looks just too hard. There ought to be a better
way to find the right sip executable, even under Linux. Suggestions?

Comments?

I tried basic usage under Windows and Linux and it works. Can we this
included in future SIP versions? I think people will be delighted to use
distutils to build their SIP-based extensions.
-- 
Giovanni Bajo
-------------- next part --------------
# Subclasses disutils.command.build_ext,
# replacing it with a SIP version that compiles .sip -> .cpp
# before calling the original build_ext command.
# Based on Pyrex.Distutils, written by Graham Fawcett and Darrel Gallion.

import distutils.command.build_ext
from distutils.dep_util import newer
import os
import sys

def replace_suffix(path, new_suffix):
    return os.path.splitext(path)[0] + new_suffix

class build_ext (distutils.command.build_ext.build_ext):

    description = "Compiler SIP descriptions, then build C/C++ extensions (compile/link to build directory)"

    def _get_sip_output_list(self, sbf):
        """
        Parse the sbf file specified to extract the name of the generated source
        files. Make them absolute assuming they reside in the temp directory.
        """
        for L in file(sbf):
            key, value = L.split("=", 1)
            if key.strip() == "sources":
                out = []
                for o in value.split():
                    out.append(os.path.join(self.build_temp, o))
                return out

        raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf

    def _find_sip(self):
        if os.name in ["nt", "cygwin"]:
            sip = "sip.exe"
        else:
            sip = "sip"

        # Try looking in the exec_prefix directory (which seems to be
        # where it usually resides).
        if os.path.isfile(os.path.join(sys.exec_prefix, sip)):
            return os.path.join(sys.exec_prefix, sip)

        # Assume it's in the PATH: self.spawn() will find it there.
        return sip

    def swig_sources (self, sources, extension=None):
        if not self.extensions:
            return

        # Create the temporary directory if it does not exist already
        if not os.path.isdir(self.build_temp):
            os.makedirs(self.build_temp)

        # Collect the names of the source (.sip) files
        sip_sources = []
        sip_sources = [source for source in sources if source.endswith('.sip')]
        other_sources = [source for source in sources if not source.endswith('.sip')]
        generated_sources = []

        for sip in sip_sources:
            # Use the sbf file as dependency check
            sipbn = os.path.basename(sip)
            sbf = os.path.join(self.build_temp, replace_suffix(sipbn, "sbf"))
            if newer(sip, sbf) or self.force:
              self.sip_compile(sip, sbf)
              out = self._get_sip_output_list(sbf)
              generated_sources.extend(out)

        return generated_sources + other_sources

    def sip_compile(self, source, sbf):
        self.spawn(["sip",
                    "-c", self.build_temp,
                    "-b", sbf,
                    source])


More information about the PyQt mailing list