rust: Add a module wrapper for bindgen

This has a couple of advantages over rolling it by hand:
1. it correctly handles include_directories objects, which is always
   handy
2. it correctly generates a depfile for you, which makes it more
   reliable
3. it requires less typing
pull/8162/head
Dylan Baker 4 years ago
parent 9d441d26d2
commit b28235428d
  1. 50
      docs/markdown/Rust-module.md
  2. 3
      docs/markdown/snippets/unstable-rust-module.md
  3. 80
      mesonbuild/modules/unstable_rust.py
  4. 6
      test cases/rust/12 bindgen/include/other.h
  5. 82
      test cases/rust/12 bindgen/meson.build
  6. 19
      test cases/rust/12 bindgen/src/gen_header.py
  7. 8
      test cases/rust/12 bindgen/src/header.h
  8. 14
      test cases/rust/12 bindgen/src/main.rs
  9. 15
      test cases/rust/12 bindgen/src/main2.rs
  10. 8
      test cases/rust/12 bindgen/src/source.c
  11. 39
      test cases/rust/12 bindgen/sub/meson.build

@ -3,7 +3,7 @@ short-description: Rust language integration module
authors:
- name: Dylan Baker
email: dylan@pnwbakers.com
years: [2020]
years: [2020, 2021]
...
# Unstable Rust module
@ -33,3 +33,51 @@ that automatically.
Additional, test only dependencies may be passed via the dependencies
argument.
### bindgen(*, input: string | BuildTarget | []string | []BuildTarget, output: strng, include_directories: []include_directories, c_args: []string, args: []string)
This function wraps bindgen to simplify creating rust bindings around C
libraries. This has two advantages over hand-rolling ones own with a
`generator` or `custom_target`:
- It handles `include_directories`, so one doesn't have to manually convert them to `-I...`
- It automatically sets up a depfile, making the results more reliable
It takes the following keyword arguments
- input — A list of Files, Strings, or CustomTargets. The first element is
the header bindgen will parse, additional elements are dependencies.
- output — the name of the output rust file
- include_directories — A list of `include_directories` objects, these are
passed to clang as `-I` arguments
- c_args — A list of string arguments to pass to clang untouched
- args — A list of string arguments to pass to `bindgen` untouched.
```meson
rust = import('unstable-rust')
inc = include_directories('..'¸ '../../foo')
generated = rust.bindgen(
'myheader.h',
'generated.rs',
include_directories : [inc, include_directories('foo'),
args : ['--no-rustfmt-bindings'],
c_args : ['-DFOO=1'],
)
```
If the header depeneds on generated headers, those headers must be passed to
`bindgen` as well to ensure proper dependency ordering, static headers do not
need to be passed, as a proper depfile is generated:
```meson
h1 = custom_target(...)
h2 = custom_target(...)
r1 = rust.bindgen(
[h1, h2], # h1 includes h2,
'out.rs',
)
```

@ -1,4 +1,5 @@
## Unstable Rust module
A new unstable module has been added to make using Rust with Meson easier.
Currently it adds a single function to ease defining Rust tests.
Currently, it adds a single function to ease defining Rust tests, as well as a
wrapper around bindgen, making it easier to use.

@ -12,18 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import typing as T
from . import ExtensionModule, ModuleReturnValue
from .. import mlog
from ..build import BuildTarget, Executable, InvalidArguments
from ..build import BuildTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments, IncludeDirs, CustomTarget
from ..dependencies import Dependency, ExternalLibrary
from ..interpreter import ExecutableHolder, BuildTargetHolder, permitted_kwargs
from ..interpreter import ExecutableHolder, BuildTargetHolder, CustomTargetHolder, permitted_kwargs, noPosargs
from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, typed_pos_args
from ..mesonlib import stringlistify, unholder, listify
from ..mesonlib import stringlistify, unholder, listify, typeslistify, File
if T.TYPE_CHECKING:
from ..interpreter import ModuleState, Interpreter
from ..dependencies import ExternalProgram
class RustModule(ExtensionModule):
@ -33,6 +35,7 @@ class RustModule(ExtensionModule):
@FeatureNew('rust module', '0.57.0')
def __init__(self, interpreter: 'Interpreter') -> None:
super().__init__(interpreter)
self._bindgen_bin: T.Optional['ExternalProgram'] = None
@permittedKwargs(permitted_kwargs['test'] | {'dependencies'} ^ {'protocol'})
@typed_pos_args('rust.test', str, BuildTargetHolder)
@ -127,6 +130,77 @@ class RustModule(ExtensionModule):
return ModuleReturnValue([], [e, test])
@noPosargs
@permittedKwargs({'input', 'output', 'include_directories', 'c_args', 'args'})
def bindgen(self, state: 'ModuleState', args: T.List, kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue:
"""Wrapper around bindgen to simplify it's use.
The main thing this simplifies is the use of `include_directory`
objects, instead of having to pass a plethora of `-I` arguments.
"""
header: T.Union[File, CustomTarget, GeneratedList, CustomTargetIndex]
_deps: T.Sequence[T.Union[File, CustomTarget, GeneratedList, CustomTargetIndex]]
try:
header, *_deps = unholder(self.interpreter.source_strings_to_files(listify(kwargs['input'])))
except KeyError:
raise InvalidArguments('rustmod.bindgen() `input` argument must have at least one element.')
try:
output: str = kwargs['output']
except KeyError:
raise InvalidArguments('rustmod.bindgen() `output` must be provided')
if not isinstance(output, str):
raise InvalidArguments('rustmod.bindgen() `output` argument must be a string.')
include_dirs: T.List[IncludeDirs] = typeslistify(unholder(listify(kwargs.get('include_directories', []))), IncludeDirs)
c_args: T.List[str] = stringlistify(listify(kwargs.get('c_args', [])))
bind_args: T.List[str] = stringlistify(listify(kwargs.get('args', [])))
# Split File and Target dependencies to add pass to CustomTarget
depends: T.List[BuildTarget] = []
depend_files: T.List[File] = []
for d in _deps:
if isinstance(d, File):
depend_files.append(d)
else:
depends.append(d)
inc_strs: T.List[str] = []
for i in include_dirs:
# bindgen always uses clang, so it's safe to hardcode -I here
inc_strs.extend([f'-I{x}' for x in i.to_string_list(state.environment.get_source_dir())])
if self._bindgen_bin is None:
# there's some bugs in the interpreter typeing.
self._bindgen_bin = T.cast('ExternalProgram', self.interpreter.find_program_impl(['bindgen']).held_object)
name: str
if isinstance(header, File):
name = header.fname
else:
name = header.get_outputs()[0]
target = CustomTarget(
f'rustmod-bindgen-{name}'.replace('/', '_'),
state.subdir,
state.subproject,
{
'input': header,
'output': output,
'command': self._bindgen_bin.get_command() + [
'@INPUT@', '--output',
os.path.join(state.environment.build_dir, '@OUTPUT@')] +
bind_args + ['--'] + c_args + inc_strs +
['-MD', '-MQ', '@INPUT@', '-MF', '@DEPFILE@'],
'depfile': '@PLAINNAME@.d',
'depends': depends,
'depend_files': depend_files,
},
backend=state.backend,
)
return ModuleReturnValue([target], [CustomTargetHolder(target, self.interpreter)])
def initialize(*args: T.List, **kwargs: T.Dict) -> RustModule:
return RustModule(*args, **kwargs) # type: ignore

@ -0,0 +1,6 @@
// SPDX-license-identifer: Apache-2.0
// Copyright © 2021 Intel Corporation
# pragma once
#include <stdint.h>

@ -0,0 +1,82 @@
# SPDX-license-identifer: Apache-2.0
# Copyright © 2021 Intel Corporation
project('rustmod bindgen', ['c', 'rust'])
prog_bindgen = find_program('bindgen', required : false)
if not prog_bindgen.found()
error('MESON_SKIP_TEST bindgen not found')
endif
# This seems to happen on windows when libclang.dll is not in path or is not
# valid. We must try to process a header file for this to work.
#
# See https://github.com/rust-lang/rust-bindgen/issues/1797
result = run_command(prog_bindgen, 'include/other.h')
if result.returncode() != 0
error('MESON_SKIP_TEST bindgen does not seem to work')
endif
# This is to test the include_directories argument to bindgen
inc = include_directories('include')
c_lib = static_library('clib', 'src/source.c', include_directories : inc)
rust = import('unstable-rust')
gen = rust.bindgen(
input : 'src/header.h',
output : 'header.rs',
include_directories : inc,
)
# see: https://github.com/mesonbuild/meson/issues/8160
f = configure_file(
input : 'src/main.rs',
output : 'main.rs',
copy : true,
)
rust_bin = executable(
'rust_bin',
[f, gen],
link_with : c_lib,
)
test('main', rust_bin)
# Test a generated header
gen_h = custom_target(
'gen.h',
command : [find_program('src/gen_header.py'), '@INPUT@', '@OUTPUT@'],
output : 'gen.h',
input : 'src/header.h'
)
gen2_h = custom_target(
'other.h',
command : [find_program('src/gen_header.py'), '@INPUT@', '@OUTPUT@'],
output : 'other.h',
input : 'include/other.h'
)
gen_rs = rust.bindgen(
input : [gen_h, gen2_h],
output : 'gen.rs',
)
f = configure_file(
input : 'src/main2.rs',
output : 'main2.rs',
copy : true,
)
rust_bin2 = executable(
'rust_bin2',
[f, gen_rs],
link_with : c_lib,
)
test('generated header', rust_bin2)
subdir('sub')

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# SPDX-license-identifer: Apache-2.0
# Copyright © 2021 Intel Corporation
import argparse
import shutil
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('output')
args = parser.parse_args()
shutil.copy2(args.input, args.output)
if __name__ == "__main__":
main()

@ -0,0 +1,8 @@
// SPDX-license-identifer: Apache-2.0
// Copyright © 2021 Intel Corporation
#pragma once
#include "other.h"
int32_t add(const int32_t, const int32_t);

@ -0,0 +1,14 @@
// SPDX-license-identifer: Apache-2.0
// Copyright © 2021 Intel Corporation
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!("header.rs");
fn main() {
unsafe {
std::process::exit(add(0, 0));
};
}

@ -0,0 +1,15 @@
// SPDX-license-identifer: Apache-2.0
// Copyright © 2021 Intel Corporation
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!("gen.rs");
fn main() {
unsafe {
std::process::exit(add(0, 0));
};
}

@ -0,0 +1,8 @@
// SPDX-license-identifer: Apache-2.0
// Copyright © 2021 Intel Corporation
#include "header.h"
int32_t add(const int32_t first, const int32_t second) {
return first + second;
}

@ -0,0 +1,39 @@
gen_rs3 = rust.bindgen(
input : [gen_h, gen2_h],
output : 'gen.rs',
)
f3 = configure_file(
input : '../src/main2.rs',
output : 'main3.rs',
copy : true,
)
rust_bin3 = executable(
'rust_bin3',
[f3, gen_rs3],
link_with : c_lib,
)
test('generated header (subdir)', rust_bin3)
gen4 = rust.bindgen(
input : '../src/header.h',
output : 'header.rs',
include_directories : inc,
)
# see: https://github.com/mesonbuild/meson/issues/8160
f4 = configure_file(
input : '../src/main.rs',
output : 'main.rs',
copy : true,
)
rust_bin4 = executable(
'rust_bin_subdir',
[f4, gen4],
link_with : c_lib,
)
test('static header (subdir)', rust_bin4)
Loading…
Cancel
Save