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