Skip to content

Commit

Permalink
WIP: modules/rust: add cbindgen helper
Browse files Browse the repository at this point in the history
TODO: needs docs
TODO: needs tests
TODO: should we handle GeneratedSources?
TODO: should we add cpp-compat always?
  • Loading branch information
dcbaker committed Jan 30, 2024
1 parent 692766b commit 0b67680
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 5 deletions.
6 changes: 5 additions & 1 deletion mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2012-2021 The Meson development team
# Copyright © 2023 Intel Corporation
# Copyright © 2023-2024 Intel Corporation

from __future__ import annotations

Expand Down Expand Up @@ -3090,6 +3090,10 @@ def source_strings_to_files(self, sources: T.List['SourceInputs'], strict: bool
@T.overload
def source_strings_to_files(self, sources: T.List[SourcesVarargsType], strict: bool = True) -> T.List['SourceOutputs']: ... # noqa: F811

@T.overload
def source_strings_to_files(self, sources: T.List[T.Union[mesonlib.FileOrString, build.CustomTarget, build.CustomTargetIndex, build.StructuredSources]], strict: bool = True) -> \
T.List[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.StructuredSources]]: ... # noqa: F811

def source_strings_to_files(self, sources: T.List['SourceInputs'], strict: bool = True) -> T.List['SourceOutputs']: # noqa: F811
"""Lower inputs to a list of Targets and Files, replacing any strings.
Expand Down
92 changes: 88 additions & 4 deletions mesonbuild/modules/rust.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2020-2023 Intel Corporation
# Copyright © 2020-2024 Intel Corporation

from __future__ import annotations
import itertools
Expand All @@ -13,9 +13,9 @@
from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList,
CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary)
from ..compilers.compilers import are_asserts_disabled
from ..interpreter.type_checking import DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS
from ..interpreter.type_checking import DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, in_set_validator
from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs
from ..mesonlib import File
from ..mesonlib import File, MesonException, version_compare

if T.TYPE_CHECKING:
from . import ModuleState
Expand All @@ -26,8 +26,9 @@
from ..interpreter.interpreter import SourceInputs, SourceOutputs
from ..programs import ExternalProgram, OverrideProgram
from ..interpreter.type_checking import SourcesVarargsType
from ..utils.universal import FileOrString

from typing_extensions import TypedDict
from typing_extensions import Literal, TypedDict

class FuncTest(_kwargs.BaseTest):

Expand All @@ -45,6 +46,19 @@ class FuncBindgen(TypedDict):
output: str
dependencies: T.List[T.Union[Dependency, ExternalLibrary]]

class FuncCBindgen(TypedDict):

config: str
language: Literal['c', 'cpp', 'cython']


def _cbindgen_config_validator(val: str) -> T.Optional[str]:
if os.path.splitext(val) != '.toml':
return 'config file must be a .toml file'
if not os.path.exists(val):
return f'config file {val} doesn\'t exist'
return None


class RustModule(ExtensionModule):

Expand All @@ -55,10 +69,13 @@ class RustModule(ExtensionModule):
def __init__(self, interpreter: Interpreter) -> None:
super().__init__(interpreter)
self._bindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
self._cbindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
self._cbindgen_has_depfile = False
self.methods.update({
'test': self.test,
'bindgen': self.bindgen,
'proc_macro': self.proc_macro,
'cbindgen': self.cbindgen,
})

@typed_pos_args('rust.test', str, BuildTarget)
Expand Down Expand Up @@ -286,6 +303,73 @@ def proc_macro(self, state: ModuleState, args: T.Tuple[str, SourcesVarargsType],
target = state._interpreter.build_target(state.current_node, args, kwargs, SharedLibrary)
return target

@FeatureNew('rust.cbindgen', '1.3.0')
@typed_pos_args('rust.cbindgen', (File, StructuredSources, CustomTargetIndex, CustomTarget, str), str)
@typed_kwargs(
'rust.cbindgen',
KwargInfo('config', str, required=True, validator=_cbindgen_config_validator),
KwargInfo(
'language',
str,
default='c',
validator=in_set_validator({'c', 'cpp', 'cython'}),
),
# TODO: depends
# TODO: depend_files
)
def cbindgen(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, CustomTarget, CustomTargetIndex, StructuredSources], str], kwargs: FuncCBindgen) -> ModuleReturnValue:
# TODO: should we allow GeneratedList here?

if self._cbindgen_bin is None:
self._cbindgen_bin = state.find_program('cbindgen')
self._cbindgen_has_depfile = version_compare(self._cbindgen_bin.get_version(self.interpreter), '>= 0.25')

_infile, outfile = args
infile = self.interpreter.source_strings_to_files([_infile])[0]

if isinstance(infile, StructuredSources):
infile = infile.first_file()
assert not isinstance(infile, GeneratedList), 'not yet? supported'
# TODO: need to depend on the rest of the StructuredSource

if isinstance(infile, File):
name = infile.fname
else:
assert not isinstance(infile, StructuredSources), "pyright understands this, but mypy doesn't"
name = infile.get_outputs()[0]
if len(infile.get_outputs()) != 1:
raise MesonException('Cannot pass a custom_target generating more than one output to rust.cbindgen, use custom_target[index] to select the output to generate bindings for')

if os.path.dirname(outfile):
raise InvalidArguments('outfile name must not contain a path segment')

# Set the --profile flag based on meson's debug option
debug = state.get_option('debug')
assert isinstance(debug, bool), 'for mypy'

# Convert Meson's `cpp` to cbindgen's `c++`
lang: str = kwargs['language']
if lang == 'cpp':
lang = 'c++'

command = [self._cbindgen_bin, '--output', '@OUTPUT@', '--config', '@INPUT0@', '--quiet', '--lang', lang, '--cpp-compat']
if self._cbindgen_has_depfile:
command.extend(['--depfile', '@DEPFILE@'])
command.extend(['--profile', 'debug' if debug else 'release'])

target = CustomTarget(
f'rustmod-cbindgen-{name}',
state.subdir,
state.subproject,
state.environment,
command,
[kwargs['config'], infile],
[outfile],
depfile=f'{name}.d',
)

return ModuleReturnValue(target, [target])


def initialize(interp: Interpreter) -> RustModule:
return RustModule(interp)

0 comments on commit 0b67680

Please sign in to comment.