Commit 9797efe0 authored by Jeroen Demeyer's avatar Jeroen Demeyer

Add support for create_extension() hook

parent af4737d7
...@@ -637,6 +637,20 @@ def create_dependency_tree(ctx=None, quiet=False): ...@@ -637,6 +637,20 @@ def create_dependency_tree(ctx=None, quiet=False):
return _dep_tree return _dep_tree
# If this changes, change also docs/src/reference/compilation.rst
# which mentions this function
def default_create_extension(template, kwds):
if 'depends' in kwds:
include_dirs = kwds.get('include_dirs', []) + ["."]
depends = resolve_depends(kwds['depends'], include_dirs)
kwds['depends'] = sorted(set(depends + template.depends))
t = template.__class__
ext = t(**kwds)
metadata = dict(distutils=kwds, module_name=kwds['name'])
return (ext, metadata)
# This may be useful for advanced users? # This may be useful for advanced users?
def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=False, language=None, def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=False, language=None,
exclude_failures=False): exclude_failures=False):
...@@ -669,13 +683,16 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet= ...@@ -669,13 +683,16 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=
Extension_distutils = Extension Extension_distutils = Extension
class Extension_setuptools(Extension): pass class Extension_setuptools(Extension): pass
# if no create_extension() function is defined, use a simple
# default function.
create_extension = ctx.options.create_extension or default_create_extension
for pattern in patterns: for pattern in patterns:
if isinstance(pattern, str): if isinstance(pattern, str):
filepattern = pattern filepattern = pattern
template = None template = Extension(pattern, []) # Fake Extension without sources
name = '*' name = '*'
base = None base = None
exn_type = Extension
ext_language = language ext_language = language
elif isinstance(pattern, (Extension_distutils, Extension_setuptools)): elif isinstance(pattern, (Extension_distutils, Extension_setuptools)):
cython_sources = [s for s in pattern.sources cython_sources = [s for s in pattern.sources
...@@ -693,7 +710,6 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet= ...@@ -693,7 +710,6 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=
template = pattern template = pattern
name = template.name name = template.name
base = DistutilsInfo(exn=template) base = DistutilsInfo(exn=template)
exn_type = template.__class__
ext_language = None # do not override whatever the Extension says ext_language = None # do not override whatever the Extension says
else: else:
msg = str("pattern is not of type str nor subclass of Extension (%s)" msg = str("pattern is not of type str nor subclass of Extension (%s)"
...@@ -727,39 +743,29 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet= ...@@ -727,39 +743,29 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=
if key not in kwds: if key not in kwds:
kwds[key] = value kwds[key] = value
kwds['name'] = module_name
sources = [file] sources = [file]
if template is not None: sources += [m for m in template.sources if m != filepattern]
sources += [m for m in template.sources if m != filepattern]
if 'sources' in kwds: if 'sources' in kwds:
# allow users to add .c files etc. # allow users to add .c files etc.
for source in kwds['sources']: for source in kwds['sources']:
source = encode_filename_in_py2(source) source = encode_filename_in_py2(source)
if source not in sources: if source not in sources:
sources.append(source) sources.append(source)
extra_sources = kwds['sources'] kwds['sources'] = sources
del kwds['sources']
else:
extra_sources = None
if 'depends' in kwds:
depends = resolve_depends(kwds['depends'], (kwds.get('include_dirs') or []) + ["."])
if template is not None:
# Always include everything from the template.
depends = set(template.depends).union(depends)
# Sort depends to make the metadata dump in the
# Cython-generated C code predictable.
kwds['depends'] = sorted(depends)
if ext_language and 'language' not in kwds: if ext_language and 'language' not in kwds:
kwds['language'] = ext_language kwds['language'] = ext_language
module_list.append(exn_type( # Create the new extension
name=module_name, m, metadata = create_extension(template, kwds)
sources=sources, module_list.append(m)
**kwds))
if extra_sources: # Store metadata (this will be written as JSON in the
kwds['sources'] = extra_sources # generated C file but otherwise has no purpose)
module_metadata[module_name] = {'distutils': kwds, 'module_name': module_name} module_metadata[module_name] = metadata
m = module_list[-1]
if file not in m.sources: if file not in m.sources:
# Old setuptools unconditionally replaces .pyx with .c # Old setuptools unconditionally replaces .pyx with .c
m.sources.remove(file.rsplit('.')[0] + '.c') m.sources.remove(file.rsplit('.')[0] + '.c')
......
...@@ -753,4 +753,5 @@ default_options = dict( ...@@ -753,4 +753,5 @@ default_options = dict(
output_dir=None, output_dir=None,
build_dir=None, build_dir=None,
cache=None, cache=None,
create_extension=None,
) )
...@@ -126,11 +126,15 @@ both might disagree about the class to use here. ...@@ -126,11 +126,15 @@ both might disagree about the class to use here.
If your options are static (for example you do not need to call a tool like If your options are static (for example you do not need to call a tool like
``pkg-config`` to determine them) you can also provide them directly in your ``pkg-config`` to determine them) you can also provide them directly in your
.pyx source file using a special comment block at the start of the file:: .pyx or .pxd source file using a special comment block at the start of the file::
# distutils: libraries = spam eggs # distutils: libraries = spam eggs
# distutils: include_dirs = /opt/food/include # distutils: include_dirs = /opt/food/include
If you cimport multiple .pxd files defining libraries, then Cython
merges the list of libraries, so this works as expected (similarly
with other options, like ``include_dirs`` above).
If you have some C files that have been wrapped with Cython and you want to If you have some C files that have been wrapped with Cython and you want to
compile them into your extension, you can define the distutils ``sources`` compile them into your extension, you can define the distutils ``sources``
parameter:: parameter::
...@@ -160,6 +164,57 @@ to find the ``.h`` and library files when linking to external libraries. ...@@ -160,6 +164,57 @@ to find the ``.h`` and library files when linking to external libraries.
.. _distutils documentation: http://docs.python.org/extending/building.html .. _distutils documentation: http://docs.python.org/extending/building.html
Sometimes this is not enough and you need finer customization of the
distutils :class:`Extension`.
To do this, you can provide a custom function ``create_extension``
to create the final :class:`Extension` object after Cython has processed
the sources, dependencies and ``# distutils`` directives but before the
file is actually Cythonized.
This function takes 2 arguments ``template`` and ``kwds``, where
``template`` is the :class:`Extension` object given as input to Cython
and ``kwds`` is a :class:`dict` with all keywords which should be used
to create the :class:`Extension`.
The function ``create_extension`` must return a 2-tuple
``(extension, metadata)``, where ``extension`` is the created
:class:`Extension` and ``metadata`` is metadata which will be written
as JSON at the top of the generated C files. This metadata is only used
for debugging purposes, so you can put whatever you want in there
(as long as it can be converted to JSON).
The default function (defined in ``Cython.Build.Dependencies``) is::
def default_create_extension(template, kwds):
if 'depends' in kwds:
include_dirs = kwds.get('include_dirs', []) + ["."]
depends = resolve_depends(kwds['depends'], include_dirs)
kwds['depends'] = sorted(set(depends + template.depends))
t = template.__class__
ext = t(**kwds)
metadata = dict(distutils=kwds, module_name=kwds['name'])
return (ext, metadata)
In case that you pass a string instead of an :class:`Extension` to
``cythonize()``, the ``template`` will be an :class:`Extension` without
sources. For example, if you do ``cythonize("*.pyx")``,
the ``template`` will be ``Extension(name="*.pyx", sources=[])``.
Just as an example, this adds ``mylib`` as library to every extension::
from Cython.Build.Dependencies import default_create_extension
def my_create_extension(template, kwds):
libs = kwds.get('libraries', []) + ["mylib"]
kwds['libraries'] = libs
return default_create_extension(template, kwds)
ext_modules = cythonize(..., create_extension=my_create_extension)
.. note::
If you Cythonize in parallel (using the ``nthreads`` argument),
then the argument to ``create_extension`` must be pickleable.
In particular, it cannot be a lambda function.
Distributing Cython modules Distributing Cython modules
---------------------------- ----------------------------
......
PYTHON setup.py build_ext --inplace
######## setup.py ########
from Cython.Build import cythonize
from Cython.Distutils.extension import Extension
ext_modules = [
Extension("foo", ["foo.pyx"]),
]
# Example documented in docs/src/reference/compilation.rst
from Cython.Build.Dependencies import default_create_extension
def my_create_extension(template, kwds):
libs = kwds.get('libraries', []) + ["mylib"]
kwds['libraries'] = libs
return default_create_extension(template, kwds)
ext_modules = cythonize(ext_modules, create_extension=my_create_extension)
assert ext_modules[0].libraries == ["mylib"]
######## foo.pyx ########
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment