Merge pull request #8706 from dcbaker/wip/2021-04/cython-language
1st class Cython language supportpull/8844/head
commit
40e8a67a83
25 changed files with 480 additions and 33 deletions
@ -0,0 +1,33 @@ |
|||||||
|
--- |
||||||
|
title: Cython |
||||||
|
short-description: Support for Cython in Meson |
||||||
|
... |
||||||
|
|
||||||
|
# Cython |
||||||
|
|
||||||
|
Meson provides native support for cython programs starting with version 0.59.0. |
||||||
|
This means that you can include it as a normal language, and create targets like |
||||||
|
any other supported language: |
||||||
|
|
||||||
|
```meson |
||||||
|
lib = static_library( |
||||||
|
'foo', |
||||||
|
'foo.pyx', |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
Generally Cython is most useful when combined with the python module's |
||||||
|
extension_module method: |
||||||
|
|
||||||
|
```meson |
||||||
|
project('my project', 'cython') |
||||||
|
|
||||||
|
py = import('python') |
||||||
|
dep_py3 = py.dependency() |
||||||
|
|
||||||
|
py.extension_module( |
||||||
|
'foo', |
||||||
|
'foo.pyx', |
||||||
|
dependencies : dep_py, |
||||||
|
) |
||||||
|
``` |
@ -0,0 +1,18 @@ |
|||||||
|
## Cython as as first class language |
||||||
|
|
||||||
|
Meson now supports Cython as a first class language. This means you can write: |
||||||
|
|
||||||
|
```meson |
||||||
|
project('my project', 'cython') |
||||||
|
|
||||||
|
py = import('python') |
||||||
|
dep_py3 = py.dependency() |
||||||
|
|
||||||
|
py.extension_module( |
||||||
|
'foo', |
||||||
|
'foo.pyx', |
||||||
|
dependencies : dep_py, |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
And avoid the step through a generator that was previously required. |
@ -0,0 +1,79 @@ |
|||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
# Copyright © 2021 Intel Corporation |
||||||
|
|
||||||
|
"""Abstraction for Cython language compilers.""" |
||||||
|
|
||||||
|
import typing as T |
||||||
|
|
||||||
|
from .. import coredata |
||||||
|
from ..mesonlib import EnvironmentException, OptionKey |
||||||
|
from .compilers import Compiler |
||||||
|
|
||||||
|
if T.TYPE_CHECKING: |
||||||
|
from ..coredata import KeyedOptionDictType |
||||||
|
from ..environment import Environment |
||||||
|
|
||||||
|
|
||||||
|
class CythonCompiler(Compiler): |
||||||
|
|
||||||
|
"""Cython Compiler.""" |
||||||
|
|
||||||
|
language = 'cython' |
||||||
|
id = 'cython' |
||||||
|
|
||||||
|
def needs_static_linker(self) -> bool: |
||||||
|
# We transpile into C, so we don't need any linker |
||||||
|
return False |
||||||
|
|
||||||
|
def get_always_args(self) -> T.List[str]: |
||||||
|
return ['--fast-fail'] |
||||||
|
|
||||||
|
def get_werror_args(self) -> T.List[str]: |
||||||
|
return ['-Werror'] |
||||||
|
|
||||||
|
def get_output_args(self, outputname: str) -> T.List[str]: |
||||||
|
return ['-o', outputname] |
||||||
|
|
||||||
|
def get_optimization_args(self, optimization_level: str) -> T.List[str]: |
||||||
|
# Cython doesn't have optimization levels itself, the underlying |
||||||
|
# compiler might though |
||||||
|
return [] |
||||||
|
|
||||||
|
def sanity_check(self, work_dir: str, environment: 'Environment') -> None: |
||||||
|
code = 'print("hello world")' |
||||||
|
with self.cached_compile(code, environment.coredata) as p: |
||||||
|
if p.returncode != 0: |
||||||
|
raise EnvironmentException(f'Cython compiler {self.id!r} cannot compile programs') |
||||||
|
|
||||||
|
def get_buildtype_args(self, buildtype: str) -> T.List[str]: |
||||||
|
# Cython doesn't implement this, but Meson requires an implementation |
||||||
|
return [] |
||||||
|
|
||||||
|
def get_pic_args(self) -> T.List[str]: |
||||||
|
# We can lie here, it's fine |
||||||
|
return [] |
||||||
|
|
||||||
|
def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], |
||||||
|
build_dir: str) -> T.List[str]: |
||||||
|
new: T.List[str] = [] |
||||||
|
for i in parameter_list: |
||||||
|
new.append(i) |
||||||
|
|
||||||
|
return new |
||||||
|
|
||||||
|
def get_options(self) -> 'KeyedOptionDictType': |
||||||
|
opts = super().get_options() |
||||||
|
opts.update({ |
||||||
|
OptionKey('version', machine=self.for_machine, lang=self.language): coredata.UserComboOption( |
||||||
|
'Python version to target', |
||||||
|
['2', '3'], |
||||||
|
'3', |
||||||
|
) |
||||||
|
}) |
||||||
|
return opts |
||||||
|
|
||||||
|
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: |
||||||
|
args: T.List[str] = [] |
||||||
|
key = options[OptionKey('version', machine=self.for_machine, lang=self.language)] |
||||||
|
args.append(f'-{key.value}') |
||||||
|
return args |
@ -0,0 +1,19 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
|
||||||
|
from storer import Storer |
||||||
|
|
||||||
|
s = Storer() |
||||||
|
|
||||||
|
if s.get_value() != 0: |
||||||
|
raise SystemExit('Initial value incorrect.') |
||||||
|
|
||||||
|
s.set_value(42) |
||||||
|
|
||||||
|
if s.get_value() != 42: |
||||||
|
raise SystemExit('Setting value failed.') |
||||||
|
|
||||||
|
try: |
||||||
|
s.set_value('not a number') |
||||||
|
raise SystemExit('Using wrong argument type did not fail.') |
||||||
|
except TypeError: |
||||||
|
pass |
@ -0,0 +1,9 @@ |
|||||||
|
|
||||||
|
cdef extern from "storer.h": |
||||||
|
ctypedef struct Storer: |
||||||
|
pass |
||||||
|
|
||||||
|
Storer* storer_new(); |
||||||
|
void storer_destroy(Storer *s); |
||||||
|
int storer_get_value(Storer *s); |
||||||
|
void storer_set_value(Storer *s, int v); |
@ -0,0 +1,8 @@ |
|||||||
|
slib = py3.extension_module( |
||||||
|
'storer', |
||||||
|
'storer.pyx', |
||||||
|
'storer.c', |
||||||
|
dependencies : py3_dep |
||||||
|
) |
||||||
|
|
||||||
|
pydir = meson.current_build_dir() |
@ -0,0 +1,24 @@ |
|||||||
|
#include"storer.h" |
||||||
|
#include<stdlib.h> |
||||||
|
|
||||||
|
struct _Storer { |
||||||
|
int value; |
||||||
|
}; |
||||||
|
|
||||||
|
Storer* storer_new() { |
||||||
|
Storer *s = malloc(sizeof(struct _Storer)); |
||||||
|
s->value = 0; |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
void storer_destroy(Storer *s) { |
||||||
|
free(s); |
||||||
|
} |
||||||
|
|
||||||
|
int storer_get_value(Storer *s) { |
||||||
|
return s->value; |
||||||
|
} |
||||||
|
|
||||||
|
void storer_set_value(Storer *s, int v) { |
||||||
|
s->value = v; |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
typedef struct _Storer Storer; |
||||||
|
|
||||||
|
Storer* storer_new(); |
||||||
|
void storer_destroy(Storer *s); |
||||||
|
int storer_get_value(Storer *s); |
||||||
|
void storer_set_value(Storer *s, int v); |
@ -0,0 +1,16 @@ |
|||||||
|
cimport cstorer |
||||||
|
|
||||||
|
cdef class Storer: |
||||||
|
cdef cstorer.Storer* _c_storer |
||||||
|
|
||||||
|
def __cinit__(self): |
||||||
|
self._c_storer = cstorer.storer_new() |
||||||
|
|
||||||
|
def __dealloc__(self): |
||||||
|
cstorer.storer_destroy(self._c_storer) |
||||||
|
|
||||||
|
cpdef int get_value(self): |
||||||
|
return cstorer.storer_get_value(self._c_storer) |
||||||
|
|
||||||
|
cpdef set_value(self, int value): |
||||||
|
cstorer.storer_set_value(self._c_storer, value) |
@ -0,0 +1,20 @@ |
|||||||
|
project( |
||||||
|
'basic cython project', |
||||||
|
['cython', 'c'], |
||||||
|
default_options : ['warning_level=3'] |
||||||
|
) |
||||||
|
|
||||||
|
py_mod = import('python') |
||||||
|
py3 = py_mod.find_installation() |
||||||
|
py3_dep = py3.dependency(required : false) |
||||||
|
if not py3_dep.found() |
||||||
|
error('MESON_SKIP_TEST: Python library not found.') |
||||||
|
endif |
||||||
|
|
||||||
|
subdir('libdir') |
||||||
|
|
||||||
|
test('cython tester', |
||||||
|
py3, |
||||||
|
args : files('cytest.py'), |
||||||
|
env : ['PYTHONPATH=' + pydir] |
||||||
|
) |
@ -0,0 +1,2 @@ |
|||||||
|
cpdef func(): |
||||||
|
return "Hello, World!" |
@ -0,0 +1,2 @@ |
|||||||
|
cpdef func(): |
||||||
|
return "Hello, World!" |
@ -0,0 +1,14 @@ |
|||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
import argparse |
||||||
|
import textwrap |
||||||
|
|
||||||
|
parser = argparse.ArgumentParser() |
||||||
|
parser.add_argument('output') |
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
with open(args.output, 'w') as f: |
||||||
|
f.write(textwrap.dedent('''\ |
||||||
|
cpdef func(): |
||||||
|
return "Hello, World!" |
||||||
|
''')) |
@ -0,0 +1,12 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
import argparse |
||||||
|
|
||||||
|
parser = argparse.ArgumentParser() |
||||||
|
parser.add_argument('input') |
||||||
|
parser.add_argument('output') |
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
with open(args.input, 'r') as i, open(args.output, 'w') as o: |
||||||
|
o.write(i.read()) |
@ -0,0 +1,61 @@ |
|||||||
|
project( |
||||||
|
'generated cython sources', |
||||||
|
['cython'], |
||||||
|
) |
||||||
|
|
||||||
|
py_mod = import('python') |
||||||
|
py3 = py_mod.find_installation('python3') |
||||||
|
py3_dep = py3.dependency(required : false) |
||||||
|
if not py3_dep.found() |
||||||
|
error('MESON_SKIP_TEST: Python library not found.') |
||||||
|
endif |
||||||
|
|
||||||
|
ct = custom_target( |
||||||
|
'ct', |
||||||
|
input : 'gen.py', |
||||||
|
output : 'ct.pyx', |
||||||
|
command : [py3, '@INPUT@', '@OUTPUT@'], |
||||||
|
) |
||||||
|
|
||||||
|
ct_ext = py3.extension_module('ct', ct, dependencies : py3_dep) |
||||||
|
|
||||||
|
test( |
||||||
|
'custom target', |
||||||
|
py3, |
||||||
|
args : [files('test.py'), 'ct'], |
||||||
|
env : ['PYTHONPATH=' + meson.current_build_dir()] |
||||||
|
) |
||||||
|
|
||||||
|
cf = configure_file( |
||||||
|
input : 'configure.pyx.in', |
||||||
|
output : 'cf.pyx', |
||||||
|
copy : true, |
||||||
|
) |
||||||
|
|
||||||
|
cf_ext = py3.extension_module('cf', cf, dependencies : py3_dep) |
||||||
|
|
||||||
|
test( |
||||||
|
'configure file', |
||||||
|
py3, |
||||||
|
args : [files('test.py'), 'cf'], |
||||||
|
env : ['PYTHONPATH=' + meson.current_build_dir()] |
||||||
|
) |
||||||
|
|
||||||
|
gen = generator( |
||||||
|
find_program('generator.py'), |
||||||
|
arguments : ['@INPUT@', '@OUTPUT@'], |
||||||
|
output : '@BASENAME@.pyx', |
||||||
|
) |
||||||
|
|
||||||
|
g_ext = py3.extension_module( |
||||||
|
'g', |
||||||
|
gen.process('g.in'), |
||||||
|
dependencies : py3_dep, |
||||||
|
) |
||||||
|
|
||||||
|
test( |
||||||
|
'generator', |
||||||
|
py3, |
||||||
|
args : [files('test.py'), 'g'], |
||||||
|
env : ['PYTHONPATH=' + meson.current_build_dir()] |
||||||
|
) |
@ -0,0 +1,13 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
import argparse |
||||||
|
import importlib |
||||||
|
|
||||||
|
parser = argparse.ArgumentParser() |
||||||
|
parser.add_argument('mod') |
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
mod = importlib.import_module(args.mod) |
||||||
|
|
||||||
|
assert mod.func() == 'Hello, World!' |
Loading…
Reference in new issue