Commit c35aae95 authored by Adrien Guinet's avatar Adrien Guinet

Make cythonize understand the np_pythran directive

This makes Pythran numpy backend only activated when such directive is
used. Update the documentation accordingly.
parent bffece4a
...@@ -50,6 +50,7 @@ except: ...@@ -50,6 +50,7 @@ except:
PythranAvailable = False PythranAvailable = False
from distutils.extension import Extension from distutils.extension import Extension
from distutils.util import strtobool
from .. import Utils from .. import Utils
from ..Utils import (cached_function, cached_method, path_exists, from ..Utils import (cached_function, cached_method, path_exists,
...@@ -163,6 +164,7 @@ def parse_list(s): ...@@ -163,6 +164,7 @@ def parse_list(s):
transitive_str = object() transitive_str = object()
transitive_list = object() transitive_list = object()
bool_or = object()
distutils_settings = { distutils_settings = {
'name': str, 'name': str,
...@@ -179,6 +181,7 @@ distutils_settings = { ...@@ -179,6 +181,7 @@ distutils_settings = {
'export_symbols': list, 'export_symbols': list,
'depends': transitive_list, 'depends': transitive_list,
'language': transitive_str, 'language': transitive_str,
'np_pythran': bool_or
} }
...@@ -210,19 +213,23 @@ class DistutilsInfo(object): ...@@ -210,19 +213,23 @@ class DistutilsInfo(object):
if line[0] != '#': if line[0] != '#':
break break
line = line[1:].lstrip() line = line[1:].lstrip()
if line[:10] == 'distutils:': kind = next((k for k in ("distutils:","cython:") if line.startswith(k)), None)
key, _, value = [s.strip() for s in line[10:].partition('=')] if not kind is None:
type = distutils_settings[key] key, _, value = [s.strip() for s in line[len(kind):].partition('=')]
type = distutils_settings.get(key, None)
if line.startswith("cython:") and type is None: continue
if type in (list, transitive_list): if type in (list, transitive_list):
value = parse_list(value) value = parse_list(value)
if key == 'define_macros': if key == 'define_macros':
value = [tuple(macro.split('=', 1)) value = [tuple(macro.split('=', 1))
if '=' in macro else (macro, None) if '=' in macro else (macro, None)
for macro in value] for macro in value]
if type is bool_or:
value = strtobool(value)
self.values[key] = value self.values[key] = value
elif exn is not None: elif exn is not None:
for key in distutils_settings: for key in distutils_settings:
if key in ('name', 'sources'): if key in ('name', 'sources','np_pythran'):
continue continue
value = getattr(exn, key, None) value = getattr(exn, key, None)
if value: if value:
...@@ -244,6 +251,8 @@ class DistutilsInfo(object): ...@@ -244,6 +251,8 @@ class DistutilsInfo(object):
all.append(v) all.append(v)
value = all value = all
self.values[key] = value self.values[key] = value
elif type is bool_or:
self.values[key] = self.values.get(key, False) | value
return self return self
def subs(self, aliases): def subs(self, aliases):
...@@ -763,8 +772,30 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet= ...@@ -763,8 +772,30 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=
if ext_language and 'language' not in kwds: if ext_language and 'language' not in kwds:
kwds['language'] = ext_language kwds['language'] = ext_language
np_pythran = kwds.pop('np_pythran', False)
# Create the new extension # Create the new extension
m, metadata = create_extension(template, kwds) m, metadata = create_extension(template, kwds)
if np_pythran:
if not PythranAvailable:
raise RuntimeError("You first need to install Pythran to use the np_pythran directive.")
pythran_ext = pythran.config.make_extension()
m.include_dirs.extend(pythran_ext['include_dirs'])
m.extra_compile_args.extend(pythran_ext['extra_compile_args'])
m.extra_link_args.extend(pythran_ext['extra_link_args'])
m.define_macros.extend(pythran_ext['define_macros'])
m.undef_macros.extend(pythran_ext['undef_macros'])
m.library_dirs.extend(pythran_ext['library_dirs'])
m.libraries.extend(pythran_ext['libraries'])
# These options are not compatible with the way normal Cython extensions work
try:
m.extra_compile_args.remove("-fwhole-program")
except ValueError: pass
try:
m.extra_compile_args.remove("-fvisibility=hidden")
except ValueError: pass
m.language = 'c++'
m.np_pythran = np_pythran
module_list.append(m) module_list.append(m)
# Store metadata (this will be written as JSON in the # Store metadata (this will be written as JSON in the
...@@ -781,7 +812,7 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet= ...@@ -781,7 +812,7 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=
# This is the user-exposed entry point. # This is the user-exposed entry point.
def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, force=False, language=None, def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, force=False, language=None,
exclude_failures=False, np_pythran=False, **options): exclude_failures=False, **options):
""" """
Compile a set of source modules into C/C++ files and return a list of distutils Compile a set of source modules into C/C++ files and return a list of distutils
Extension objects for them. Extension objects for them.
...@@ -806,8 +837,6 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, ...@@ -806,8 +837,6 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
that this only really makes sense for compiling .py files which can also that this only really makes sense for compiling .py files which can also
be used without compilation. be used without compilation.
To use the Pythran backend for numpy operations, set np_pythran to True.
Additional compilation options can be passed as keyword arguments. Additional compilation options can be passed as keyword arguments.
""" """
if exclude is None: if exclude is None:
...@@ -818,9 +847,7 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, ...@@ -818,9 +847,7 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
if options.get('cache'): if options.get('cache'):
raise NotImplementedError("common_utility_include_dir does not yet work with caching") raise NotImplementedError("common_utility_include_dir does not yet work with caching")
safe_makedirs(options['common_utility_include_dir']) safe_makedirs(options['common_utility_include_dir'])
if np_pythran: if PythranAvailable:
if not PythranAvailable:
raise RuntimeError("You first need to install Pythran to use the np_pythran flag.")
pythran_options = CompilationOptions(**options); pythran_options = CompilationOptions(**options);
pythran_options.cplus = True pythran_options.cplus = True
pythran_options.np_pythran = True pythran_options.np_pythran = True
...@@ -860,25 +887,9 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False, ...@@ -860,25 +887,9 @@ def cythonize(module_list, exclude=None, nthreads=0, aliases=None, quiet=False,
for source in m.sources: for source in m.sources:
base, ext = os.path.splitext(source) base, ext = os.path.splitext(source)
if ext in ('.pyx', '.py'): if ext in ('.pyx', '.py'):
if np_pythran: if m.np_pythran:
c_file = base + '.cpp' c_file = base + '.cpp'
options = pythran_options options = pythran_options
pythran_ext = pythran.config.make_extension()
m.include_dirs.extend(pythran_ext['include_dirs'])
m.extra_compile_args.extend(pythran_ext['extra_compile_args'])
m.extra_link_args.extend(pythran_ext['extra_link_args'])
m.define_macros.extend(pythran_ext['define_macros'])
m.undef_macros.extend(pythran_ext['undef_macros'])
m.library_dirs.extend(pythran_ext['library_dirs'])
m.libraries.extend(pythran_ext['libraries'])
# These options are not compatible with the way normal Cython plugins work
try:
m.extra_compile_args.remove("-fwhole-program")
except ValueError: pass
try:
m.extra_compile_args.remove("-fvisibility=hidden")
except ValueError: pass
m.language = 'c++'
elif m.language == 'c++': elif m.language == 'c++':
c_file = base + '.cpp' c_file = base + '.cpp'
options = cpp_options options = cpp_options
......
...@@ -22,10 +22,10 @@ Usage example with distutils ...@@ -22,10 +22,10 @@ Usage example with distutils
You first need to install Pythran. See its `documentation You first need to install Pythran. See its `documentation
<https://pythonhosted.org/pythran/MANUAL.html>`_ for more information. <https://pythonhosted.org/pythran/MANUAL.html>`_ for more information.
Then, simply add ``np_pythran=True`` to the ``cythonize`` call in the related Then, simply add a ``cython: np_pythran=True`` directive at the top of the
setup.py. Python files that needs to be compiled using Pythran numpy support.
Here is an example: Here is an example of a simple ``setup.py`` file using distutils:
.. code:: .. code::
...@@ -34,8 +34,22 @@ Here is an example: ...@@ -34,8 +34,22 @@ Here is an example:
setup( setup(
name = "My hello app", name = "My hello app",
ext_modules = cythonize('hello_pythran.pyx', np_pythran=True) ext_modules = cythonize('hello_pythran.pyx')
) )
Then, with the following header in ``hello_pythran.pyx``:
.. code::
# cython: np_pythran=True
``hello_pythran.pyx`` will be compiled using Pythran numpy support.
Please note that Pythran can further be tweaked by adding settings in the
``$HOME/.pythranrc`` file. For instance, this can be used to enable `Boost.SIMD`_ support.
See the `Pythran user manual
<https://pythonhosted.org/pythran/MANUAL.html#customizing-your-pythranrc>`_ for
more information.
.. _Pythran: https://github.com/serge-sans-paille/pythran .. _Pythran: https://github.com/serge-sans-paille/pythran
.. _Boost.SIMD: https://github.com/NumScale/boost.simd
...@@ -881,7 +881,7 @@ class CythonCompileTestCase(unittest.TestCase): ...@@ -881,7 +881,7 @@ class CythonCompileTestCase(unittest.TestCase):
extra_extension_args = {} extra_extension_args = {}
if self.pythran_dir is not None: if self.pythran_dir is not None:
ext_compile_flags.extend(['-I',self.pythran_dir,'-DENABLE_PYTHON_MODULE','-std=c++11','-D__PYTHRAN__=2','-Wno-cpp']) ext_compile_flags.extend(['-I',self.pythran_dir,'-DENABLE_PYTHON_MODULE','-std=c++11','-D__PYTHRAN__=%d' % sys.version_info.major,'-Wno-cpp'])
related_files = self.related_files(test_directory, module) related_files = self.related_files(test_directory, module)
self.copy_files(test_directory, workdir, related_files) self.copy_files(test_directory, workdir, related_files)
......
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