mirror of
https://github.com/mesonbuild/meson.git
synced 2026-08-09 09:12:24 +00:00
rust: Add wrapper for cbindgen
This is pretty minimal, but does work at least for basic tests. It also handles structured_sources. Has been tested against Mesa's use of cbindgen. Fixes: #13092
This commit is contained in:
committed by
Paolo Bonzini
parent
da76d46d9b
commit
6a04534897
@@ -143,6 +143,45 @@ were never turned on by Meson.
|
||||
bindgen_clang_arguments = ['--target', 'x86_64-linux-gnu']
|
||||
```
|
||||
|
||||
## cbindgen()
|
||||
|
||||
*Since 1.12.0*
|
||||
|
||||
```meson
|
||||
file_h = rustmod.cbindgen('infile.rs', 'outfile.h', config : 'cbindgen.toml')
|
||||
```
|
||||
|
||||
This function wraps cbindgen to simplify creating C bindings for Rust targets.
|
||||
It has several advantages over invoking a `custom_target` directly:
|
||||
|
||||
- Correctly handles depfiles, adding them when supported
|
||||
- Sets the profile to `debug` when Meson is configured with -Ddebug=true
|
||||
- Automatically passes some useful options for use in Meson, such as `--quiet`
|
||||
and `--cpp-compat`
|
||||
- Handles structured_sources correctly
|
||||
- Automatically detect language based on file extension (c or c++ only),
|
||||
|
||||
The majority of the configuration should be handled inside the `config.toml`
|
||||
file.
|
||||
|
||||
It takes the following positional arguments:
|
||||
|
||||
- `infile`: the root rust file to bind, or a [[@structured_src]] instance
|
||||
- `outfile`: the header to generate
|
||||
|
||||
It takes the following keyword arguments
|
||||
|
||||
- `config`: The Path to a configuration toml. May be a File, str, or CustomTarget.
|
||||
Customarily, this is called `cbindgen.toml`, but Meson does not require this.
|
||||
- `language`: The language to bind for. If unset, Meson will choose a language
|
||||
based on the file extension of the output file. Currently, auto detection only
|
||||
works for C and C++. May be one of: `c`, `cpp`, `cython`.
|
||||
- `depends`: An array of CustomTargets that this target depends on
|
||||
- `depend_files`: An array of files that this target depends on
|
||||
- `install`: A boolean controlling whether to install the generated header
|
||||
- `install_dir`: Where to install the header. This is required if `install` is true.
|
||||
|
||||
|
||||
### compiler_target()
|
||||
|
||||
*Since 1.11.0*
|
||||
|
||||
5
docs/markdown/snippets/rust-cbindgen.md
Normal file
5
docs/markdown/snippets/rust-cbindgen.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## The Rust module includes a basic wrapper for cbindgen
|
||||
|
||||
This will correctly track the config.toml, and generate a rust source from the
|
||||
marked C files. This handles structured_sources correctly, and also handles
|
||||
depfile generation transparently.
|
||||
@@ -21,8 +21,8 @@ from ..dependencies import Dependency
|
||||
from ..interpreter.decorators import apply_machine_map
|
||||
from ..interpreter.type_checking import (
|
||||
DEPENDENCIES_KW, LINK_WITH_KW, LINK_WHOLE_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS,
|
||||
NATIVE_KW, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator,
|
||||
EXECUTABLE_KWS, LIBRARY_KWS, SHARED_MOD_KWS, _BASE_LANG_KW
|
||||
OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NATIVE_KW, NoneType, in_set_validator,
|
||||
EXECUTABLE_KWS, LIBRARY_KWS, SHARED_MOD_KWS, _BASE_LANG_KW, DEPEND_FILES_KW, INSTALL_DIR_KW, INSTALL_KW,
|
||||
)
|
||||
from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noKwargs, noPosargs
|
||||
from ..interpreter.interpreterobjects import Doctest
|
||||
@@ -45,8 +45,9 @@ if T.TYPE_CHECKING:
|
||||
from ..interpreterbase import TYPE_kwargs
|
||||
from ..programs import Program
|
||||
from ..interpreter.type_checking import SourcesVarargsType
|
||||
from ..utils.universal import FileOrString
|
||||
|
||||
from typing_extensions import TypedDict, Literal
|
||||
from typing_extensions import Literal, TypedDict
|
||||
|
||||
ArgsType = T.TypeVar('ArgsType')
|
||||
|
||||
@@ -73,6 +74,15 @@ if T.TYPE_CHECKING:
|
||||
language: T.Optional[Literal['c', 'cpp']]
|
||||
bindgen_version: T.List[str]
|
||||
|
||||
class FuncCBindgen(TypedDict):
|
||||
|
||||
config: str | File | CustomTarget | CustomTargetIndex
|
||||
language: T.Literal['c', 'cpp', 'cython'] | None
|
||||
depends: list[TargetDepends]
|
||||
depend_files: list[FileOrString]
|
||||
install: bool
|
||||
install_dir: str | None
|
||||
|
||||
class FuncSubproject(TypedDict):
|
||||
native: MachineChoice
|
||||
|
||||
@@ -583,6 +593,12 @@ class RustSubproject(RustCrate):
|
||||
return state.overridden_dependency(depname, for_machine=self.for_machine)
|
||||
|
||||
|
||||
def _cbindgen_config_validator(val: str) -> T.Optional[str]:
|
||||
if os.path.splitext(val)[1] != '.toml':
|
||||
return 'config file must be a .toml file'
|
||||
return None
|
||||
|
||||
|
||||
class RustModule(ExtensionModule):
|
||||
|
||||
"""A module that holds helper functions for rust."""
|
||||
@@ -600,6 +616,10 @@ class RustModule(ExtensionModule):
|
||||
else:
|
||||
self._bindgen_rust_target = None
|
||||
self._bindgen_set_std = False
|
||||
|
||||
self._cbindgen_bin: Program | None = None
|
||||
self._cbindgen_has_depfile = False
|
||||
|
||||
self.methods.update({
|
||||
'test': self.test,
|
||||
'doctest': self.doctest,
|
||||
@@ -608,6 +628,7 @@ class RustModule(ExtensionModule):
|
||||
'proc_macro': self.proc_macro,
|
||||
'to_system_dependency': self.to_system_dependency,
|
||||
'workspace': self.workspace,
|
||||
'cbindgen': self.cbindgen,
|
||||
})
|
||||
|
||||
def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncRustTest) -> T.Tuple[Executable, _kwargs.FuncTest]:
|
||||
@@ -1100,6 +1121,121 @@ class RustModule(ExtensionModule):
|
||||
|
||||
return RustWorkspace(self.interpreter, ws)
|
||||
|
||||
@FeatureNew('rust.cbindgen', '1.12.0')
|
||||
@typed_pos_args('rust.cbindgen', (str, File, CustomTargetIndex, CustomTarget, StructuredSources), str)
|
||||
@typed_kwargs(
|
||||
'rust.cbindgen',
|
||||
KwargInfo('config', (str, File, CustomTarget, CustomTargetIndex), required=True, validator=_cbindgen_config_validator),
|
||||
KwargInfo(
|
||||
'language',
|
||||
(str, NoneType),
|
||||
validator=in_set_validator({'c', 'cpp', 'cython'}),
|
||||
),
|
||||
KwargInfo(
|
||||
'depends',
|
||||
ContainerTypeInfo(list, (CustomTarget, CustomTargetIndex)),
|
||||
default=[],
|
||||
listify=True,
|
||||
),
|
||||
DEPEND_FILES_KW,
|
||||
INSTALL_KW,
|
||||
INSTALL_DIR_KW,
|
||||
)
|
||||
def cbindgen(self, state: ModuleState,
|
||||
args: tuple[FileOrString | CustomTarget | CustomTargetIndex | StructuredSources, str],
|
||||
kwargs: FuncCBindgen) -> ModuleReturnValue:
|
||||
# TODO: should we allow GeneratedList here?
|
||||
|
||||
if kwargs['install'] and not kwargs['install_dir']:
|
||||
raise InterpreterException.from_node('cbindgen: When `install` is true `install_dir` must be set',
|
||||
node=state.current_node)
|
||||
|
||||
if self._cbindgen_bin is None:
|
||||
self._cbindgen_bin = state.find_program('cbindgen')
|
||||
self._cbindgen_has_depfile = mesonlib.version_compare(
|
||||
self._cbindgen_bin.get_version(self.interpreter), '>= 0.25')
|
||||
|
||||
_infile, outfile = args
|
||||
infile = self.interpreter.source_strings_to_files([_infile])[0]
|
||||
depend_files = self.interpreter.source_strings_to_files(kwargs['depend_files'])
|
||||
depends = kwargs['depends'].copy()
|
||||
|
||||
if isinstance(infile, StructuredSources):
|
||||
infile, *_depends = infile.as_list()
|
||||
if isinstance(infile, GeneratedList):
|
||||
raise MesonException.from_node(
|
||||
'Using a GeneratedList as the main input for cbindgen is unsupported',
|
||||
node=state.current_node)
|
||||
for d in _depends:
|
||||
if isinstance(d, File):
|
||||
depend_files.append(d)
|
||||
else:
|
||||
depends.append(d)
|
||||
|
||||
if isinstance(infile, File):
|
||||
name = infile.fname
|
||||
else:
|
||||
if len(infile.get_outputs()) != 1:
|
||||
raise mesonlib.MesonException.from_node(
|
||||
'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',
|
||||
node=state.current_node)
|
||||
name = infile.get_outputs()[0]
|
||||
|
||||
if os.path.dirname(outfile):
|
||||
raise InvalidArguments.from_node(
|
||||
'outfile name must not contain a path segment', node=state.current_node)
|
||||
|
||||
# Detect langauge from output file extension
|
||||
language = kwargs['language']
|
||||
if language is None:
|
||||
ext = os.path.splitext(outfile)[1][1:]
|
||||
if ext in lang_suffixes['cpp']:
|
||||
language = 'cpp'
|
||||
elif ext == 'h':
|
||||
language = 'c'
|
||||
else:
|
||||
raise InterpreterException.from_node(
|
||||
f'Unknown file type extension for: {outfile}', node=state.current_node)
|
||||
|
||||
# Convert Meson's `cpp` to cbindgen's `c++`
|
||||
if language == 'cpp':
|
||||
language = 'c++'
|
||||
|
||||
# Set the --profile flag based on meson's debug option
|
||||
debug = state.get_option('debug')
|
||||
assert isinstance(debug, bool), 'for mypy'
|
||||
|
||||
command: list[str | Program] = [
|
||||
self._cbindgen_bin, '--output', '@OUTPUT@', '--config',
|
||||
'@INPUT0@', '--quiet', '--lang', language,
|
||||
'--profile', 'debug' if debug else 'release',
|
||||
]
|
||||
if language == 'c':
|
||||
command.append('--cpp-compat')
|
||||
if self._cbindgen_has_depfile:
|
||||
command.extend(['--depfile', '@DEPFILE@'])
|
||||
command.extend(['--', '@INPUT1@']) # must be last
|
||||
|
||||
config_file = self.interpreter.source_strings_to_files([kwargs['config']])
|
||||
|
||||
target = CustomTarget(
|
||||
f'rustmod-cbindgen-{outfile}',
|
||||
state.subdir,
|
||||
state.environment,
|
||||
command,
|
||||
[config_file[0], infile],
|
||||
[outfile],
|
||||
state.current_build_project,
|
||||
depfile=f'{name}.d',
|
||||
depend_files=depend_files,
|
||||
extra_depends=depends,
|
||||
install=kwargs['install'],
|
||||
install_dir=[kwargs['install_dir']],
|
||||
install_tag=['devel'],
|
||||
)
|
||||
|
||||
return ModuleReturnValue(target, [target])
|
||||
|
||||
|
||||
def initialize(interp: Interpreter) -> RustModule:
|
||||
return RustModule(interp)
|
||||
|
||||
159
test cases/rust/44 cbindgen/config.toml
Normal file
159
test cases/rust/44 cbindgen/config.toml
Normal file
@@ -0,0 +1,159 @@
|
||||
# This is a template cbindgen.toml file with all of the default values.
|
||||
# Some values are commented out because their absence is the real default.
|
||||
#
|
||||
# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml
|
||||
# for detailed documentation of every option here.
|
||||
|
||||
|
||||
|
||||
language = "C++"
|
||||
|
||||
|
||||
|
||||
############## Options for Wrapping the Contents of the Header #################
|
||||
|
||||
# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
|
||||
# trailer = "/* Text to put at the end of the generated file */"
|
||||
# include_guard = "my_bindings_h"
|
||||
pragma_once = true
|
||||
# autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
include_version = false
|
||||
# namespace = "my_namespace"
|
||||
namespaces = []
|
||||
using_namespaces = []
|
||||
sys_includes = []
|
||||
includes = []
|
||||
no_includes = false
|
||||
# cpp_compat = true
|
||||
after_includes = ""
|
||||
|
||||
|
||||
|
||||
|
||||
############################ Code Style Options ################################
|
||||
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
documentation = true
|
||||
documentation_style = "auto"
|
||||
documentation_length = "full"
|
||||
line_endings = "LF" # also "CR", "CRLF", "Native"
|
||||
|
||||
|
||||
|
||||
|
||||
############################# Codegen Options ##################################
|
||||
|
||||
style = "both"
|
||||
sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
|
||||
usize_is_size_t = true
|
||||
|
||||
|
||||
|
||||
[defines]
|
||||
# "target_os = freebsd" = "DEFINE_FREEBSD"
|
||||
# "feature = serde" = "DEFINE_SERDE"
|
||||
|
||||
|
||||
|
||||
[export]
|
||||
include = []
|
||||
exclude = []
|
||||
# prefix = "CAPI_"
|
||||
item_types = []
|
||||
renaming_overrides_prefixing = false
|
||||
|
||||
|
||||
|
||||
[export.rename]
|
||||
|
||||
|
||||
|
||||
[export.body]
|
||||
|
||||
|
||||
[export.mangle]
|
||||
|
||||
|
||||
[fn]
|
||||
rename_args = "None"
|
||||
# must_use = "MUST_USE_FUNC"
|
||||
# deprecated = "DEPRECATED_FUNC"
|
||||
# deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE"
|
||||
# no_return = "NO_RETURN"
|
||||
# prefix = "START_FUNC"
|
||||
# postfix = "END_FUNC"
|
||||
args = "auto"
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
|
||||
|
||||
[struct]
|
||||
rename_fields = "None"
|
||||
# must_use = "MUST_USE_STRUCT"
|
||||
# deprecated = "DEPRECATED_STRUCT"
|
||||
# deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE"
|
||||
derive_constructor = false
|
||||
derive_eq = false
|
||||
derive_neq = false
|
||||
derive_lt = false
|
||||
derive_lte = false
|
||||
derive_gt = false
|
||||
derive_gte = false
|
||||
|
||||
|
||||
|
||||
|
||||
[enum]
|
||||
rename_variants = "None"
|
||||
# must_use = "MUST_USE_ENUM"
|
||||
# deprecated = "DEPRECATED_ENUM"
|
||||
# deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE"
|
||||
add_sentinel = false
|
||||
prefix_with_name = false
|
||||
derive_helper_methods = false
|
||||
derive_const_casts = false
|
||||
derive_mut_casts = false
|
||||
# cast_assert_name = "ASSERT"
|
||||
derive_tagged_enum_destructor = false
|
||||
derive_tagged_enum_copy_constructor = false
|
||||
enum_class = true
|
||||
private_default_tagged_enum_constructor = false
|
||||
|
||||
|
||||
|
||||
|
||||
[const]
|
||||
allow_static_const = true
|
||||
allow_constexpr = false
|
||||
sort_by = "Name"
|
||||
|
||||
|
||||
|
||||
|
||||
[macro_expansion]
|
||||
bitflags = false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
############## Options for How Your Rust library Should Be Parsed ##############
|
||||
|
||||
[parse]
|
||||
parse_deps = false
|
||||
# include = []
|
||||
exclude = []
|
||||
clean = false
|
||||
extra_bindings = []
|
||||
|
||||
|
||||
|
||||
[parse.expand]
|
||||
crates = []
|
||||
all_features = false
|
||||
default_features = true
|
||||
features = []
|
||||
15
test cases/rust/44 cbindgen/lib.rs
Normal file
15
test cases/rust/44 cbindgen/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright © 2024 Intel Corporation
|
||||
*/
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MyStruct {
|
||||
pub cost: i64,
|
||||
pub power: u8,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn print(x: &MyStruct) {
|
||||
println!("A thing has a cost of {} and a power of {}", x.cost, x.power);
|
||||
}
|
||||
12
test cases/rust/44 cbindgen/lib2.rs
Normal file
12
test cases/rust/44 cbindgen/lib2.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright © 2024 Intel Corporation
|
||||
*/
|
||||
|
||||
mod sub;
|
||||
use sub::MyStruct;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn print(x: &MyStruct) {
|
||||
println!("A thing has a cost of {} and a power of {}", x.cost, x.power);
|
||||
}
|
||||
51
test cases/rust/44 cbindgen/meson.build
Normal file
51
test cases/rust/44 cbindgen/meson.build
Normal file
@@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright © 2024 Intel Corporation
|
||||
|
||||
project(
|
||||
'cbindgen',
|
||||
'rust', 'c',
|
||||
meson_version : '>= 1.12',
|
||||
default_options : {'cpp_std' : 'c++20'},
|
||||
)
|
||||
|
||||
if not find_program('cbindgen', required : false).found()
|
||||
error('MESON_SKIP_TEST: cbindgen was not found')
|
||||
endif
|
||||
|
||||
rust = import('rust')
|
||||
|
||||
rlib = static_library('rlib', 'lib.rs', rust_abi : 'c')
|
||||
|
||||
bound_h = rust.cbindgen('lib.rs', 'lib.h', config : 'config.toml')
|
||||
|
||||
test(
|
||||
'uses bound rust',
|
||||
executable('ctest', 'test1.c', bound_h, link_with : rlib)
|
||||
)
|
||||
|
||||
srcs = structured_sources(
|
||||
'lib2.rs',
|
||||
{
|
||||
'sub' : 'sub.rs',
|
||||
}
|
||||
)
|
||||
|
||||
rlib2 = static_library('lib2', srcs, rust_abi : 'c')
|
||||
|
||||
bound_h = rust.cbindgen(srcs, 'lib2.h', config : 'config.toml')
|
||||
test(
|
||||
'uses bound rust from structured_sources',
|
||||
executable('ctest2', 'test1.c', bound_h, c_args : '-DUSE_LIB2', link_with : rlib2)
|
||||
)
|
||||
|
||||
# The requirement of C++20 for designated initializers means this may not be
|
||||
# supported everywhere
|
||||
if add_languages('cpp', native : false, required : false)
|
||||
rlib3 = static_library('lib3', 'lib.rs', rust_abi : 'c')
|
||||
|
||||
bound_h = rust.cbindgen('lib.rs', 'lib3.hpp', config : 'config.toml')
|
||||
test(
|
||||
'generates a c++ header',
|
||||
executable('cpptest1', 'test1.cpp', bound_h, link_with : rlib2)
|
||||
)
|
||||
endif
|
||||
10
test cases/rust/44 cbindgen/sub.rs
Normal file
10
test cases/rust/44 cbindgen/sub.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright © 2024 Intel Corporation
|
||||
*/
|
||||
|
||||
#[repr(C)]
|
||||
pub struct MyStruct {
|
||||
pub cost: i64,
|
||||
pub power: u8,
|
||||
}
|
||||
20
test cases/rust/44 cbindgen/test1.c
Normal file
20
test cases/rust/44 cbindgen/test1.c
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright © 2024 Intel Corporation
|
||||
*/
|
||||
|
||||
#ifdef USE_LIB2
|
||||
#include "lib2.h"
|
||||
#else
|
||||
#include "lib.h"
|
||||
#endif
|
||||
|
||||
static MyStruct thing = {
|
||||
.cost = -5,
|
||||
.power = 1,
|
||||
};
|
||||
|
||||
int main() {
|
||||
print(&thing);
|
||||
return 0;
|
||||
}
|
||||
16
test cases/rust/44 cbindgen/test1.cpp
Normal file
16
test cases/rust/44 cbindgen/test1.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright © 2024 Intel Corporation
|
||||
*/
|
||||
|
||||
#include "lib3.hpp"
|
||||
|
||||
static MyStruct thing = {
|
||||
.cost = -5,
|
||||
.power = 1,
|
||||
};
|
||||
|
||||
int main() {
|
||||
print(&thing);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user