setuptools: build shared libary from C++ code, then build Cython wrapper linked to shared libary

We have a bunch of C++ files with classes that we wrap to Python using Cython. We use setuptools to build the Cython extension. This all works fine, we followed the guide here

We are basically doing something like this

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize(
           "rect.pyx",                 # our Cython source
           sources=["Rectangle.cpp"],  # additional source file(s)
           language="c++",             # generate C++ code

We don't like about this that we have to recompile everything, even if only the Cython part changes, rect.pyx in this example. In fact we never touch the .cpp files, but change the .pyx files often.

We would like to compile the .cpp files separately into a static or shared libary, then build .pyx files independenty, which link to the library generated from the .cpp files. All this would be easy with make or cmake, but we want a pure Python solution that uses only setuptools. Mock-up code would look something like this:

from distutils.core import setup
from Cython.Build import cythonize

class CppLibary:
    # somehow get that to work

# this should only recompile cpplib when source files changed
cpplib = CppLibary('cpplib',
                   sources=["Rectangle.cpp"], # put static cpp code here

setup(ext_modules = cythonize(
           "rect.pyx",                 # our Cython source
           libraries=[cpplib],         # link to cpplib
           language="c++",             # generate C++ code

1 answer

  • answered 2018-03-14 11:57 danny

    There is a seemingly undocumented feature of setup that can do this, for example:

    import os
    from setuptools import setup
    from Cython.Build import cythonize
    ext_lib_path = 'rectangle'
    include_dir = os.path.join(ext_lib_path, 'include')
    sources = ['Rectangle.cpp']
    # Use as macros = [('<DEFINITION>', '<VALUE>')]
    # where value can be None
    macros = None
    ext_libraries = ['rectangle', {
                   'sources': [os.path.join(ext_lib_path, src) for src in sources],
                   'include_dirs': [include_dir],
                   'macros': macros,
    extensions = [Extension("rect",

    The libraries argument builds the external library found in directory rectangle, with include directory rectangle/include common between it and the extension.

    Have also switched the import to setuptools from distutils which is deprecated, now part of setuptools.

    Have not seen any documentation on this argument but seen it used in other projects.

    This is untested, please provide sample files for testing if it does not work.