diff --git a/docs/markdown/Cython.md b/docs/markdown/Cython.md new file mode 100644 index 000000000..17302b38a --- /dev/null +++ b/docs/markdown/Cython.md @@ -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, +) +``` diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md index eb0210e2a..f2fc663eb 100644 --- a/docs/markdown/Reference-tables.md +++ b/docs/markdown/Reference-tables.md @@ -34,6 +34,7 @@ These are return values of the `get_id` (Compiler family) and | sun | Sun Fortran compiler | | | valac | Vala compiler | | | xc16 | Microchip XC16 C compiler | | +| cython | The Cython compiler | | ## Linker ids @@ -160,6 +161,7 @@ These are the parameter names for passing language specific arguments to your bu | Objective C++ | objcpp_args | objcpp_link_args | | Rust | rust_args | rust_link_args | | Vala | vala_args | vala_link_args | +| Cython | cython_args | cython_link_args | All these `_*` options are specified per machine. See in [specifying options per @@ -177,16 +179,17 @@ are many caveats to their use, especially when rebuilding the project. It is **highly** recommended that you use [the command line arguments](#language-arguments-parameter-names) instead. -| Name | Comment | -| ----- | ------- | -| CFLAGS | Flags for the C compiler | -| CXXFLAGS | Flags for the C++ compiler | -| OBJCFLAGS | Flags for the Objective C compiler | -| FFLAGS | Flags for the Fortran compiler | -| DFLAGS | Flags for the D compiler | -| VALAFLAGS | Flags for the Vala compiler | -| RUSTFLAGS | Flags for the Rust compiler | -| LDFLAGS | The linker flags, used for all languages | +| Name | Comment | +| ----- | ------- | +| CFLAGS | Flags for the C compiler | +| CXXFLAGS | Flags for the C++ compiler | +| OBJCFLAGS | Flags for the Objective C compiler | +| FFLAGS | Flags for the Fortran compiler | +| DFLAGS | Flags for the D compiler | +| VALAFLAGS | Flags for the Vala compiler | +| RUSTFLAGS | Flags for the Rust compiler | +| CYTHONFLAGS | Flags for the Cython compiler | +| LDFLAGS | The linker flags, used for all languages | N.B. these settings are specified per machine, and so the environment varibles actually come in pairs. See the [environment variables per diff --git a/docs/markdown/snippets/first-class-cython.md b/docs/markdown/snippets/first-class-cython.md new file mode 100644 index 000000000..481269767 --- /dev/null +++ b/docs/markdown/snippets/first-class-cython.md @@ -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. diff --git a/docs/sitemap.txt b/docs/sitemap.txt index d942aa40c..8014668f4 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -59,6 +59,7 @@ index.md Java.md Vala.md D.md + Cython.md IDE-integration.md Custom-build-targets.md Build-system-converters.md diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index f69f64fd9..8efb01b33 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -749,6 +749,9 @@ int dummy; # C/C++ sources, objects, generated libs, and unknown sources now. target_sources, generated_sources, \ transpiled_sources = self.generate_vala_compile(target) + elif 'cython' in target.compilers: + target_sources, generated_sources, \ + transpiled_sources = self.generate_cython_transpile(target) else: target_sources = self.get_target_sources(target) generated_sources = self.get_target_generated_sources(target) @@ -1544,6 +1547,66 @@ int dummy; self.create_target_source_introspection(target, valac, args, all_files, []) return other_src[0], other_src[1], vala_c_src + def generate_cython_transpile(self, target: build.BuildTarget) -> \ + T.Tuple[T.MutableMapping[str, File], T.MutableMapping[str, File], T.List[str]]: + """Generate rules for transpiling Cython files to C or C++ + + XXX: Currently only C is handled. + """ + static_sources: T.MutableMapping[str, File] = OrderedDict() + generated_sources: T.MutableMapping[str, File] = OrderedDict() + cython_sources: T.List[str] = [] + + cython = target.compilers['cython'] + + opt_proxy = self.get_compiler_options_for_target(target) + + args: T.List[str] = [] + args += cython.get_always_args() + args += cython.get_buildtype_args(self.get_option_for_target(OptionKey('buildtype'), target)) + args += cython.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) + args += cython.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) + args += cython.get_option_compile_args(opt_proxy) + args += self.build.get_global_args(cython, target.for_machine) + args += self.build.get_project_args(cython, target.subproject, target.for_machine) + + for src in target.get_sources(): + if src.endswith('.pyx'): + output = os.path.join(self.get_target_private_dir(target), f'{src}.c') + args = args.copy() + args += cython.get_output_args(output) + element = NinjaBuildElement( + self.all_outputs, [output], + self.compiler_to_rule_name(cython), + [src.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())]) + element.add_item('ARGS', args) + self.add_build(element) + # TODO: introspection? + cython_sources.append(output) + else: + static_sources[src.rel_to_builddir(self.build_to_src)] = src + + for gen in target.get_generated_sources(): + for ssrc in gen.get_outputs(): + if isinstance(gen, GeneratedList): + ssrc = os.path.join(self.get_target_private_dir(target) , ssrc) + if ssrc.endswith('.pyx'): + args = args.copy() + output = os.path.join(self.get_target_private_dir(target), f'{ssrc}.c') + args += cython.get_output_args(output) + element = NinjaBuildElement( + self.all_outputs, [output], + self.compiler_to_rule_name(cython), + [ssrc]) + element.add_item('ARGS', args) + self.add_build(element) + # TODO: introspection? + cython_sources.append(output) + else: + generated_sources[ssrc] = mesonlib.File.from_built_file(gen.subdir, ssrc) + + return static_sources, generated_sources, cython_sources + def generate_rust_target(self, target: build.BuildTarget) -> None: rustc = target.compilers['rust'] # Rust compiler takes only the main file as input and @@ -1889,10 +1952,7 @@ int dummy; for for_machine in MachineChoice: complist = self.environment.coredata.compilers[for_machine] for langname, compiler in complist.items(): - if langname == 'java' \ - or langname == 'vala' \ - or langname == 'rust' \ - or langname == 'cs': + if langname in {'java', 'vala', 'rust', 'cs', 'cython'}: continue rule = '{}_LINKER{}'.format(langname, self.get_rule_suffix(for_machine)) command = compiler.get_linker_exelist() @@ -1940,6 +2000,12 @@ int dummy; description = 'Compiling Vala source $in' self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1')) + def generate_cython_compile_rules(self, compiler: 'Compiler') -> None: + rule = self.compiler_to_rule_name(compiler) + command = compiler.get_exelist() + ['$ARGS', '$in'] + description = 'Compiling Cython source $in' + self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1')) + def generate_rust_compile_rules(self, compiler): rule = self.compiler_to_rule_name(compiler) command = compiler.get_exelist() + ['$ARGS', '$in'] @@ -2012,6 +2078,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if self.environment.machines.matches_build_machine(compiler.for_machine): self.generate_swift_compile_rules(compiler) return + if langname == 'cython': + self.generate_cython_compile_rules(compiler) + return crstr = self.get_rule_suffix(compiler.for_machine) if langname == 'fortran': self.generate_fortran_dep_hack(crstr) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index a4d14d051..bc86fe3ce 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -41,9 +41,10 @@ from .interpreterbase import FeatureNew if T.TYPE_CHECKING: from ._typing import ImmutableListProtocol, ImmutableSetProtocol - from .interpreter.interpreter import Test, SourceOutputs + from .interpreter.interpreter import Test, SourceOutputs, Interpreter from .mesonlib import FileMode, FileOrString from .backend.backends import Backend + from .interpreter.interpreterobjects import GeneratorHolder pch_kwargs = {'c_pch', 'cpp_pch'} @@ -63,6 +64,7 @@ lang_arg_kwargs = { 'rust_args', 'vala_args', 'cs_args', + 'cython_args', } vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} @@ -810,7 +812,7 @@ class BuildTarget(Target): # If all our sources are Vala, our target also needs the C compiler but # it won't get added above. - if 'vala' in self.compilers and 'c' not in self.compilers: + if ('vala' in self.compilers or 'cython' in self.compilers) and 'c' not in self.compilers: self.compilers['c'] = compilers['c'] def validate_sources(self): @@ -1564,7 +1566,7 @@ class Generator: raise InvalidArguments('Depends entries must be build targets.') self.depends.append(d) - def get_base_outnames(self, inname): + def get_base_outnames(self, inname) -> T.List[str]: plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] @@ -1586,7 +1588,7 @@ class Generator: relpath = pathlib.PurePath(trial).relative_to(parent) return relpath.parts[0] != '..' # For subdirs we can only go "down". - def process_files(self, name, files, state, preserve_path_from=None, extra_args=None): + def process_files(self, name, files, state: 'Interpreter', preserve_path_from=None, extra_args=None): new = False output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) #XXX @@ -1621,14 +1623,14 @@ class Generator: class GeneratedList: - def __init__(self, generator, subdir, preserve_path_from=None, extra_args=None): + def __init__(self, generator: 'GeneratorHolder', subdir: str, preserve_path_from=None, extra_args=None): self.generator = unholder(generator) self.name = self.generator.exe self.depends = set() # Things this target depends on (because e.g. a custom target was used as input) self.subdir = subdir - self.infilelist = [] - self.outfilelist = [] - self.outmap = {} + self.infilelist: T.List['File'] = [] + self.outfilelist: T.List[str] = [] + self.outmap: T.Dict['File', str] = {} self.extra_depends = [] self.depend_files = [] self.preserve_path_from = preserve_path_from @@ -1642,17 +1644,17 @@ class GeneratedList: # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) - def add_preserved_path_segment(self, infile, outfiles, state): - result = [] + def add_preserved_path_segment(self, infile: 'File', outfiles: T.List[str], state: 'Interpreter') -> T.List[str]: + result: T.List[str] = [] in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) - assert(os.path.isabs(self.preserve_path_from)) + assert os.path.isabs(self.preserve_path_from) rel = os.path.relpath(in_abs, self.preserve_path_from) path_segment = os.path.dirname(rel) for of in outfiles: result.append(os.path.join(path_segment, of)) return result - def add_file(self, newfile, state): + def add_file(self, newfile: 'File', state: 'Interpreter') -> None: self.infilelist.append(newfile) outfiles = self.generator.get_base_outnames(newfile.fname) if self.preserve_path_from: @@ -1660,16 +1662,16 @@ class GeneratedList: self.outfilelist += outfiles self.outmap[newfile] = outfiles - def get_inputs(self): + def get_inputs(self) -> T.List['File']: return self.infilelist def get_outputs(self) -> T.List[str]: return self.outfilelist - def get_outputs_for(self, filename): + def get_outputs_for(self, filename: 'File') -> T.List[str]: return self.outmap[filename] - def get_generator(self): + def get_generator(self) -> 'Generator': return self.generator def get_extra_args(self): diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index 60cfdfed8..bd30b9bbc 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -107,6 +107,7 @@ __all__ = [ 'VisualStudioCCompiler', 'VisualStudioCPPCompiler', 'CLikeCompiler', + 'CythonCompiler', ] # Bring symbols from each module into compilers sub-package namespace @@ -213,3 +214,4 @@ from .mixins.gnu import GnuCompiler, GnuLikeCompiler from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler from .mixins.clang import ClangCompiler from .mixins.clike import CLikeCompiler +from .cython import CythonCompiler diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 361f5d6d0..cdb9f1c70 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -64,6 +64,7 @@ lang_suffixes = { 'cs': ('cs',), 'swift': ('swift',), 'java': ('java',), + 'cython': ('pyx', ), } # type: T.Dict[str, T.Tuple[str, ...]] all_languages = lang_suffixes.keys() cpp_suffixes = lang_suffixes['cpp'] + ('h',) # type: T.Tuple[str, ...] @@ -97,6 +98,7 @@ CFLAGS_MAPPING: T.Mapping[str, str] = { 'd': 'DFLAGS', 'vala': 'VALAFLAGS', 'rust': 'RUSTFLAGS', + 'cython': 'CYTHONFLAGS', } CEXE_MAPPING: T.Mapping = { diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py new file mode 100644 index 000000000..513f07995 --- /dev/null +++ b/mesonbuild/compilers/cython.py @@ -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 diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index fc9b703ac..ce037dd76 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -87,6 +87,7 @@ from .compilers import ( ClangObjCPPCompiler, ClangClCCompiler, ClangClCPPCompiler, + CythonCompiler, FlangFortranCompiler, G95FortranCompiler, GnuCCompiler, @@ -732,6 +733,7 @@ class Environment: self.default_rust = ['rustc'] self.default_swift = ['swiftc'] self.default_vala = ['valac'] + self.default_cython = [['cython']] self.default_static_linker = ['ar', 'gar'] self.default_strip = ['strip'] self.vs_static_linker = ['lib'] @@ -1757,6 +1759,30 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) + def detect_cython_compiler(self, for_machine: MachineChoice) -> CythonCompiler: + """Search for a cython compiler.""" + compilers = self.lookup_binary_entry(for_machine, 'cython') + is_cross = self.is_cross_build(for_machine) + info = self.machines[for_machine] + if compilers is None: + # TODO support fallback + compilers = [self.default_cython[0]] + + popen_exceptions: T.Dict[str, Exception] = {} + for comp in compilers: + try: + err = Popen_safe(comp + ['-V'])[2] + except OSError as e: + popen_exceptions[' '.join(comp + ['-V'])] = e + continue + + version = search_version(err) + if 'Cython' in err: + comp_class = CythonCompiler + self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) + return comp_class(comp, version, for_machine, info, is_cross=is_cross) + self._handle_exceptions(popen_exceptions, compilers) + def detect_vala_compiler(self, for_machine): exelist = self.lookup_binary_entry(for_machine, 'vala') is_cross = self.is_cross_build(for_machine) @@ -2023,6 +2049,8 @@ class Environment: comp = self.detect_fortran_compiler(for_machine) elif lang == 'swift': comp = self.detect_swift_compiler(for_machine) + elif lang == 'cython': + comp = self.detect_cython_compiler(for_machine) else: comp = None return comp diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ecaeb7a68..eff76be61 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1193,8 +1193,9 @@ external dependencies (including libraries) must go to "dependencies".''') args = [a.lower() for a in args] langs = set(self.coredata.compilers[for_machine].keys()) langs.update(args) - if 'vala' in langs and 'c' not in langs: - FeatureNew('Adding Vala language without C', '0.59.0').use(self.subproject) + if ('vala' in langs or 'cython' in langs) and 'c' not in langs: + if 'vala' in langs: + FeatureNew.single_use('Adding Vala language without C', '0.59.0', self.subproject) args.append('c') success = True diff --git a/run_project_tests.py b/run_project_tests.py index f477e6c5a..74413e607 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -54,8 +54,8 @@ from run_tests import guess_backend ALL_TESTS = ['cmake', 'common', 'native', 'warning-meson', 'failing-meson', 'failing-build', 'failing-test', 'keyval', 'platform-osx', 'platform-windows', 'platform-linux', - 'java', 'C#', 'vala', 'rust', 'd', 'objective c', 'objective c++', - 'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm' + 'java', 'C#', 'vala', 'cython', 'rust', 'd', 'objective c', 'objective c++', + 'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm', ] @@ -1016,6 +1016,7 @@ def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List TestCategory('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), TestCategory('C#', 'csharp', skip_csharp(backend)), TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))), + TestCategory('cython', 'cython', backend is not Backend.ninja or not shutil.which(os.environ.get('CYTHON', 'cython'))), TestCategory('rust', 'rust', should_skip_rust(backend)), TestCategory('d', 'd', backend is not Backend.ninja or not have_d_compiler()), TestCategory('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or not have_objc_compiler(options.use_tmpdir)), diff --git a/test cases/cython/1 basic/cytest.py b/test cases/cython/1 basic/cytest.py new file mode 100755 index 000000000..c08ffeed3 --- /dev/null +++ b/test cases/cython/1 basic/cytest.py @@ -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 diff --git a/test cases/cython/1 basic/libdir/cstorer.pxd b/test cases/cython/1 basic/libdir/cstorer.pxd new file mode 100644 index 000000000..7b730fc75 --- /dev/null +++ b/test cases/cython/1 basic/libdir/cstorer.pxd @@ -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); diff --git a/test cases/cython/1 basic/libdir/meson.build b/test cases/cython/1 basic/libdir/meson.build new file mode 100644 index 000000000..144bb1f14 --- /dev/null +++ b/test cases/cython/1 basic/libdir/meson.build @@ -0,0 +1,8 @@ +slib = py3.extension_module( + 'storer', + 'storer.pyx', + 'storer.c', + dependencies : py3_dep +) + +pydir = meson.current_build_dir() diff --git a/test cases/cython/1 basic/libdir/storer.c b/test cases/cython/1 basic/libdir/storer.c new file mode 100644 index 000000000..0199bb850 --- /dev/null +++ b/test cases/cython/1 basic/libdir/storer.c @@ -0,0 +1,24 @@ +#include"storer.h" +#include + +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; +} diff --git a/test cases/cython/1 basic/libdir/storer.h b/test cases/cython/1 basic/libdir/storer.h new file mode 100644 index 000000000..4f7191711 --- /dev/null +++ b/test cases/cython/1 basic/libdir/storer.h @@ -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); diff --git a/test cases/cython/1 basic/libdir/storer.pyx b/test cases/cython/1 basic/libdir/storer.pyx new file mode 100644 index 000000000..ed551dc5f --- /dev/null +++ b/test cases/cython/1 basic/libdir/storer.pyx @@ -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) diff --git a/test cases/cython/1 basic/meson.build b/test cases/cython/1 basic/meson.build new file mode 100644 index 000000000..8c24e2312 --- /dev/null +++ b/test cases/cython/1 basic/meson.build @@ -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] +) diff --git a/test cases/cython/2 generated sources/configure.pyx.in b/test cases/cython/2 generated sources/configure.pyx.in new file mode 100644 index 000000000..1c44f6d42 --- /dev/null +++ b/test cases/cython/2 generated sources/configure.pyx.in @@ -0,0 +1,2 @@ +cpdef func(): + return "Hello, World!" diff --git a/test cases/cython/2 generated sources/g.in b/test cases/cython/2 generated sources/g.in new file mode 100644 index 000000000..1c44f6d42 --- /dev/null +++ b/test cases/cython/2 generated sources/g.in @@ -0,0 +1,2 @@ +cpdef func(): + return "Hello, World!" diff --git a/test cases/cython/2 generated sources/gen.py b/test cases/cython/2 generated sources/gen.py new file mode 100644 index 000000000..5c0a82d56 --- /dev/null +++ b/test cases/cython/2 generated sources/gen.py @@ -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!" + ''')) diff --git a/test cases/cython/2 generated sources/generator.py b/test cases/cython/2 generated sources/generator.py new file mode 100755 index 000000000..77de85569 --- /dev/null +++ b/test cases/cython/2 generated sources/generator.py @@ -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()) diff --git a/test cases/cython/2 generated sources/meson.build b/test cases/cython/2 generated sources/meson.build new file mode 100644 index 000000000..c9a9b6a7d --- /dev/null +++ b/test cases/cython/2 generated sources/meson.build @@ -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()] +) diff --git a/test cases/cython/2 generated sources/test.py b/test cases/cython/2 generated sources/test.py new file mode 100644 index 000000000..307283f26 --- /dev/null +++ b/test cases/cython/2 generated sources/test.py @@ -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!'