diff --git a/docs/markdown/Cython.md b/docs/markdown/Cython.md index 1d30c1f97..304275043 100644 --- a/docs/markdown/Cython.md +++ b/docs/markdown/Cython.md @@ -31,3 +31,32 @@ py.extension_module( dependencies : dep_py, ) ``` + +## C++ intermediate support + +*(New in 0.60.0)* + +An option has been added to control this, called `cython_language`. This can be +either `'c'` or `'cpp'`. + +For those coming from setuptools/distutils, they will find two things. First, +meson ignores `# distutils: language = c++` inline directives. Second that Meson +allows options only on a per-target granularity. This means that if you need to mix +cython files being transpiled to C and to C++ you need two targets: + +```meson +project('my project', 'cython') + +cython_cpp_lib = static_library( + 'helper_lib', + 'foo_cpp.pyx', # will be transpiled to C++ + override_options : ['cython_language=cpp'], +) + +py.extension_module( + 'foo', + 'foo.pyx', # will be transpiled to C + link_with : [cython_cpp_lib], + dependencies : dep_py, +) +``` diff --git a/docs/markdown/snippets/cython-c++-intermediate.md b/docs/markdown/snippets/cython-c++-intermediate.md new file mode 100644 index 000000000..0a1c35fe8 --- /dev/null +++ b/docs/markdown/snippets/cython-c++-intermediate.md @@ -0,0 +1,22 @@ +## Cython can now transpile to C++ as an intermediate language + +Built-in cython support currently only allows C as an intermediate language, now +C++ is also allowed. This can be set via the `cython_language` option, either on +the command line, or in the meson.build files. + +```meson +project( + 'myproject', + 'cython', + default_options : ['cython_language=cpp'], +) +``` + +or on a per target basis with: +```meson +python.extension_module( + 'mod', + 'mod.pyx', + override_options : ['cython_language=cpp'], +) +``` diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 844c6123a..7c97ca353 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1598,9 +1598,11 @@ class NinjaBackend(backends.Backend): args += self.build.get_global_args(cython, target.for_machine) args += self.build.get_project_args(cython, target.subproject, target.for_machine) + ext = opt_proxy[OptionKey('language', machine=target.for_machine, lang='cython')].value + for src in target.get_sources(): if src.endswith('.pyx'): - output = os.path.join(self.get_target_private_dir(target), f'{src}.c') + output = os.path.join(self.get_target_private_dir(target), f'{src}.{ext}') args = args.copy() args += cython.get_output_args(output) element = NinjaBuildElement( @@ -1622,7 +1624,7 @@ class NinjaBackend(backends.Backend): ssrc = os.path.join(gen.get_subdir(), ssrc) if ssrc.endswith('.pyx'): args = args.copy() - output = os.path.join(self.get_target_private_dir(target), f'{ssrc}.c') + output = os.path.join(self.get_target_private_dir(target), f'{ssrc}.{ext}') args += cython.get_output_args(output) element = NinjaBuildElement( self.all_outputs, [output], diff --git a/mesonbuild/build.py b/mesonbuild/build.py index b5fa8ea37..149841546 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -37,7 +37,7 @@ from .mesonlib import ( ) from .compilers import ( Compiler, is_object, clink_langs, sort_clink, lang_suffixes, - is_known_suffix, detect_static_linker + is_known_suffix, detect_static_linker, detect_compiler_for ) from .linkers import StaticLinker from .interpreterbase import FeatureNew @@ -833,8 +833,40 @@ 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 or 'cython' in self.compilers) and 'c' not in self.compilers: + if 'vala' in self.compilers and 'c' not in self.compilers: self.compilers['c'] = compilers['c'] + if 'cython' in self.compilers: + key = OptionKey('language', machine=self.for_machine, lang='cython') + if key in self.option_overrides_compiler: + value = self.option_overrides_compiler[key] + else: + value = self.environment.coredata.options[key].value + + try: + self.compilers[value] = compilers[value] + except KeyError: + # TODO: it would be nice to not have to do this here, but we + # have two problems to work around: + # 1. If this is set via an override we have no way to know + # before now that we need a compiler for the non-default language + # 2. Because Cython itself initializes the `cython_language` + # option, we have no good place to insert that you need it + # before now, so we just have to do it here. + comp = detect_compiler_for(self.environment, value, self.for_machine) + + # This is copied verbatim from the interpreter + if self.for_machine == MachineChoice.HOST or self.environment.is_cross_build(): + logger_fun = mlog.log + else: + logger_fun = mlog.debug + logger_fun(comp.get_display_language(), 'compiler for the', self.for_machine.get_lower_case_name(), 'machine:', + mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string()) + if comp.linker is not None: + logger_fun(comp.get_display_language(), 'linker for the', self.for_machine.get_lower_case_name(), 'machine:', + mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version) + if comp is None: + raise MesonException(f'Cannot find required compiler {value}') + self.compilers[value] = comp def validate_sources(self): if not self.sources: diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 513f07995..34ddff1a5 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -68,6 +68,11 @@ class CythonCompiler(Compiler): 'Python version to target', ['2', '3'], '3', + ), + OptionKey('language', machine=self.for_machine, lang=self.language): coredata.UserComboOption( + 'Output C or C++ files', + ['c', 'cpp'], + 'c', ) }) return opts @@ -76,4 +81,7 @@ class CythonCompiler(Compiler): args: T.List[str] = [] key = options[OptionKey('version', machine=self.for_machine, lang=self.language)] args.append(f'-{key.value}') + lang = options[OptionKey('language', machine=self.for_machine, lang=self.language)] + if lang.value == 'cpp': + args.append('--cplus') return args diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 4d4e5da69..2d04d2dfa 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1241,9 +1241,14 @@ 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 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) + # We'd really like to add cython's default language here, but it can't + # actually be done because the cython compiler hasn't been initialized, + # so we can't actually get the option yet. Because we can't know what + # compiler to add by default, and we don't want to add unnecessary + # compilers we don't add anything for cython here, and instead do it + # When the first cython target using a particular language is used. + if 'vala' in langs and 'c' not in langs: + FeatureNew.single_use('Adding Vala language without C', '0.59.0', self.subproject) args.append('c') success = True diff --git a/test cases/cython/1 basic/libdir/storer.h b/test cases/cython/1 basic/libdir/storer.h index 4f7191711..6f5bc6fe5 100644 --- a/test cases/cython/1 basic/libdir/storer.h +++ b/test cases/cython/1 basic/libdir/storer.h @@ -1,8 +1,16 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + 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); + +#ifdef __cplusplus +} +#endif diff --git a/test cases/cython/1 basic/test.json b/test cases/cython/1 basic/test.json new file mode 100644 index 000000000..ed7ac2b9b --- /dev/null +++ b/test cases/cython/1 basic/test.json @@ -0,0 +1,10 @@ +{ + "matrix": { + "options": { + "cython_language": [ + { "val": "c" }, + { "val": "cpp" } + ] + } + } +}