aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2021-01-06 11:28:50 -0800
committerDylan Baker <dylan@pnwbakers.com>2021-02-06 10:27:04 -0800
commitb28235428df69ba0a90a8f7c0f26db8527ec68a7 (patch)
tree10df8a0b264bf493cbf7de3807e5a56a97d959ab
parent9d441d26d20a8bd79edd2180160238771fe8c28d (diff)
downloadmeson-b28235428df69ba0a90a8f7c0f26db8527ec68a7.zip
meson-b28235428df69ba0a90a8f7c0f26db8527ec68a7.tar.gz
meson-b28235428df69ba0a90a8f7c0f26db8527ec68a7.tar.bz2
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
-rw-r--r--docs/markdown/Rust-module.md50
-rw-r--r--docs/markdown/snippets/unstable-rust-module.md3
-rw-r--r--mesonbuild/modules/unstable_rust.py80
-rw-r--r--test cases/rust/12 bindgen/include/other.h6
-rw-r--r--test cases/rust/12 bindgen/meson.build82
-rw-r--r--test cases/rust/12 bindgen/src/gen_header.py19
-rw-r--r--test cases/rust/12 bindgen/src/header.h8
-rw-r--r--test cases/rust/12 bindgen/src/main.rs14
-rw-r--r--test cases/rust/12 bindgen/src/main2.rs15
-rw-r--r--test cases/rust/12 bindgen/src/source.c8
-rw-r--r--test cases/rust/12 bindgen/sub/meson.build39
11 files changed, 319 insertions, 5 deletions
diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md
index 9f217ea..fdff483 100644
--- a/docs/markdown/Rust-module.md
+++ b/docs/markdown/Rust-module.md
@@ -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',
+)
+```
diff --git a/docs/markdown/snippets/unstable-rust-module.md b/docs/markdown/snippets/unstable-rust-module.md
index e594ecf..87ffe33 100644
--- a/docs/markdown/snippets/unstable-rust-module.md
+++ b/docs/markdown/snippets/unstable-rust-module.md
@@ -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.
diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py
index e74c181..c4d7d41 100644
--- a/mesonbuild/modules/unstable_rust.py
+++ b/mesonbuild/modules/unstable_rust.py
@@ -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
diff --git a/test cases/rust/12 bindgen/include/other.h b/test cases/rust/12 bindgen/include/other.h
new file mode 100644
index 0000000..3f715f1
--- /dev/null
+++ b/test cases/rust/12 bindgen/include/other.h
@@ -0,0 +1,6 @@
+// SPDX-license-identifer: Apache-2.0
+// Copyright © 2021 Intel Corporation
+
+# pragma once
+
+#include <stdint.h>
diff --git a/test cases/rust/12 bindgen/meson.build b/test cases/rust/12 bindgen/meson.build
new file mode 100644
index 0000000..7844884
--- /dev/null
+++ b/test cases/rust/12 bindgen/meson.build
@@ -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')
diff --git a/test cases/rust/12 bindgen/src/gen_header.py b/test cases/rust/12 bindgen/src/gen_header.py
new file mode 100644
index 0000000..099e4ec
--- /dev/null
+++ b/test cases/rust/12 bindgen/src/gen_header.py
@@ -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()
diff --git a/test cases/rust/12 bindgen/src/header.h b/test cases/rust/12 bindgen/src/header.h
new file mode 100644
index 0000000..2c03f33
--- /dev/null
+++ b/test cases/rust/12 bindgen/src/header.h
@@ -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);
diff --git a/test cases/rust/12 bindgen/src/main.rs b/test cases/rust/12 bindgen/src/main.rs
new file mode 100644
index 0000000..3f85e96
--- /dev/null
+++ b/test cases/rust/12 bindgen/src/main.rs
@@ -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));
+ };
+}
diff --git a/test cases/rust/12 bindgen/src/main2.rs b/test cases/rust/12 bindgen/src/main2.rs
new file mode 100644
index 0000000..a3c28d7
--- /dev/null
+++ b/test cases/rust/12 bindgen/src/main2.rs
@@ -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));
+ };
+}
diff --git a/test cases/rust/12 bindgen/src/source.c b/test cases/rust/12 bindgen/src/source.c
new file mode 100644
index 0000000..d652d28
--- /dev/null
+++ b/test cases/rust/12 bindgen/src/source.c
@@ -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;
+}
diff --git a/test cases/rust/12 bindgen/sub/meson.build b/test cases/rust/12 bindgen/sub/meson.build
new file mode 100644
index 0000000..1da360e
--- /dev/null
+++ b/test cases/rust/12 bindgen/sub/meson.build
@@ -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)