Skip to content

Commit 3d80a88

Browse files
committed
modules: Add an unstable-rust module
Like other language specific modules this module is module for holding rust specific helpers. This commit adds a test() function, which simplifies using rust's internal unittest mechanism. Rust tests are generally placed in the same code files as they are testing, in contrast to languages like C/C++ and python which generally place the tests in separate translation units. For meson this is somewhat problematic from a repetition point of view, as the only changes are generally adding --test, and possibly some dependencies. The rustmod.test() method provides a mechanism to remove the repatition: it takes a rust target, copies it, and then addes the `--test` option, then creates a Test() target with the `rust` protocol. You can pass additional dependencies via the `dependencies` keyword. This all makes for a nice, DRY, test definition.
1 parent b2c2549 commit 3d80a88

File tree

9 files changed

+229
-27
lines changed

9 files changed

+229
-27
lines changed

docs/markdown/Rust-module.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
short-description: Rust language integration module
3+
authors:
4+
- name: Dylan Baker
5+
6+
years: [2020]
7+
...
8+
9+
# Unstable Rust module
10+
11+
*(new in 0.57.0)*
12+
13+
**Note** Unstable modules make no backwards compatible API guarantees.
14+
15+
The rust module provides helper to integrate rust code into meson. The goal
16+
is to make using rust in meson more pleasant, while still remaining mesonic,
17+
this means that it attempts to make rust work more like meson, rather than
18+
meson work more like rust.
19+
20+
## Functions
21+
22+
### test(name: string, target: library | executable, dependencies: []Dependency)
23+
24+
This function creates a new rust unittest target from an existing rust based
25+
target, which may be a library or executable. It does this by copying the
26+
sources and arguments passed to the original target and adding the `--test`
27+
argument to the compilation, then creates a new test target which calls that
28+
executable, using the rust test protocol.
29+
30+
This accepts all of the keyword arguments as the
31+
[`test`](Reference-manual.md#test) function except `protocol`, it will set
32+
that automatically.
33+
34+
Additional, test only dependencies may be passed via the dependencies
35+
argument.

docs/markdown/_Sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212
* [gnome](Gnome-module.md)
1313
* [i18n](i18n-module.md)
1414
* [pkgconfig](Pkgconfig-module.md)
15+
* [rust](Rust-module.md)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Untable Rust module
2+
3+
A new unstable module has been added to make using rust with meson easier.
4+
Currently it adds a single function to ease defining rust tests.

docs/sitemap.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,25 @@ index.md
3434
Disabler.md
3535
Modules.md
3636
CMake-module.md
37+
Cuda-module.md
3738
Dlang-module.md
39+
External-Project-module.md
3840
Fs-module.md
3941
Gnome-module.md
4042
Hotdoc-module.md
41-
i18n-module.md
4243
Icestorm-module.md
44+
Keyval-module.md
4345
Pkgconfig-module.md
44-
Python-module.md
4546
Python-3-module.md
47+
Python-module.md
4648
Qt4-module.md
4749
Qt5-module.md
4850
RPM-module.md
51+
Rust-module.md
4952
Simd-module.md
5053
SourceSet-module.md
5154
Windows-module.md
52-
Cuda-module.md
53-
Keyval-module.md
54-
External-Project-module.md
55+
i18n-module.md
5556
Java.md
5657
Vala.md
5758
D.md

docs/theme/extra/templates/navbar_links.html

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,29 @@
55
Modules <span class="caret"></span>
66
</a>
77
<ul class="dropdown-menu" id="modules-menu">
8-
@for tup in ( \
9-
("CMake-module.html","CMake"), \
10-
("Cuda-module.html","CUDA"), \
11-
("Dlang-module.html","Dlang"), \
12-
("Fs-module.html","Filesystem"), \
13-
("Gnome-module.html","GNOME"), \
14-
("Hotdoc-module.html","Hotdoc"), \
15-
("i18n-module.html","i18n"), \
16-
("Icestorm-module.html","Icestorm"), \
17-
("Keyval-module.html","Keyval"), \
18-
("Pkgconfig-module.html","Pkgconfig"), \
19-
("Python-module.html","Python"), \
20-
("Python-3-module.html","Python 3"), \
21-
("Qt4-module.html","Qt4"), \
22-
("Qt5-module.html","Qt5"), \
23-
("RPM-module.html","RPM"), \
24-
("SourceSet-module.html","SourceSet"), \
25-
("Windows-module.html","Windows")):
26-
<li>
27-
<a href="@tup[0]">@tup[1]</a>
28-
</li>
29-
@end
8+
@for tup in [ \
9+
("CMake-module.html","CMake"), \
10+
("Cuda-module.html","CUDA"), \
11+
("Dlang-module.html","Dlang"), \
12+
("Fs-module.html","Filesystem"), \
13+
("Gnome-module.html","GNOME"), \
14+
("Hotdoc-module.html","Hotdoc"), \
15+
("Icestorm-module.html","Icestorm"), \
16+
("Keyval-module.html","Keyval"), \
17+
("Pkgconfig-module.html","Pkgconfig"), \
18+
("Python-3-module.html","Python 3"), \
19+
("Python-module.html","Python"), \
20+
("Qt4-module.html","Qt4"), \
21+
("Qt5-module.html","Qt5"), \
22+
("RPM-module.html","RPM"), \
23+
("Rust-module.html","Rust"), \
24+
("SourceSet-module.html","SourceSet"), \
25+
("Windows-module.html","Windows"), \
26+
("i18n-module.html","i18n")]:
27+
<li>
28+
<a href="@tup[0]">@tup[1]</a>
29+
</li>
30+
@end
3031
</ul>
3132
</li>
3233
\

mesonbuild/modules/unstable_rust.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Copyright © 2020 Intel Corporation
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import typing as T
16+
17+
from . import ExtensionModule, ModuleReturnValue
18+
from .. import mlog
19+
from ..build import BuildTarget, Executable, InvalidArguments
20+
from ..dependencies import Dependency, ExternalLibrary
21+
from ..interpreter import ExecutableHolder, permitted_kwargs
22+
from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew
23+
from ..mesonlib import stringlistify, unholder, listify
24+
25+
if T.TYPE_CHECKING:
26+
from ..interpreter import ModuleState, Interpreter
27+
28+
29+
class RustModule(ExtensionModule):
30+
31+
"""A module that holds helper functions for rust."""
32+
33+
@FeatureNew('rust module', '0.57.0')
34+
def __init__(self, interpreter: 'Interpreter') -> None:
35+
super().__init__(interpreter)
36+
37+
@permittedKwargs(permitted_kwargs['test'] | {'dependencies'} ^ {'protocol'})
38+
def test(self, state: 'ModuleState', args: T.List, kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue:
39+
"""Generate a rust test target from a given rust target.
40+
41+
Rust puts it's unitests inside it's main source files, unlike most
42+
languages that put them in external files. This means that normally
43+
you have to define two seperate targets with basically the same
44+
arguments to get tests:
45+
46+
```meson
47+
rust_lib_sources = [...]
48+
rust_lib = static_library(
49+
'rust_lib',
50+
rust_lib_sources,
51+
)
52+
53+
rust_lib_test = executable(
54+
'rust_lib_test',
55+
rust_lib_sources,
56+
rust_args : ['--test'],
57+
)
58+
59+
test(
60+
'rust_lib_test',
61+
rust_lib_test,
62+
protocol : 'rust',
63+
)
64+
```
65+
66+
This is all fine, but not very DRY. This method makes it much easier
67+
to define rust tests:
68+
69+
```meson
70+
rust = import('unstable-rust')
71+
72+
rust_lib = static_library(
73+
'rust_lib',
74+
[sources],
75+
)
76+
77+
rust.test('rust_lib_test', rust_lib)
78+
```
79+
"""
80+
if len(args) != 2:
81+
raise InterpreterException('rustmod.test() takes exactly 2 positional arguments')
82+
name: str = args[0]
83+
if not isinstance(name, str):
84+
raise InterpreterException('First positional argument to rustmod.test() must be a string')
85+
base_target: BuildTarget = unholder(args[1])
86+
if not isinstance(base_target, BuildTarget):
87+
raise InterpreterException('Second positional argument to rustmod.test() must be a library or executable')
88+
if not base_target.get_using_rustc():
89+
raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target')
90+
extra_args = stringlistify(kwargs.get('args', []))
91+
92+
# Delete any arguments we don't want passed
93+
if '--test' in extra_args:
94+
mlog.warning('Do not add --test to rustmod.test arguments')
95+
extra_args.remove('--test')
96+
if '--format' in extra_args:
97+
mlog.warning('Do not add --format to rustmod.test arguments')
98+
i = extra_args.index('--format')
99+
# Also delete the argument to --format
100+
del extra_args[i + 1]
101+
del extra_args[i]
102+
for i, a in enumerate(extra_args):
103+
if a.startswith('--format='):
104+
del extra_args[i]
105+
break
106+
107+
dependencies = unholder(listify(kwargs.get('dependencies', [])))
108+
for d in dependencies:
109+
if not isinstance(d, (Dependency, ExternalLibrary)):
110+
raise InvalidArguments('dependencies must be a dependency or external library')
111+
112+
kwargs['args'] = extra_args + ['--test', '--format', 'pretty']
113+
kwargs['protocol'] = 'rust'
114+
115+
new_target_kwargs = base_target.kwargs.copy()
116+
# Don't mutate the shallow copied list, instead replace it with a new
117+
# one
118+
new_target_kwargs['rust_args'] = new_target_kwargs.get('rust_args', []) + ['--test']
119+
new_target_kwargs['install'] = False
120+
new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + dependencies
121+
122+
new_target = Executable(
123+
name, base_target.subdir, state.subproject,
124+
base_target.for_machine, base_target.sources,
125+
base_target.objects, base_target.environment,
126+
new_target_kwargs
127+
)
128+
129+
e = ExecutableHolder(new_target, self.interpreter)
130+
test = self.interpreter.make_test(
131+
self.interpreter.current_node, [name, e], kwargs)
132+
133+
return ModuleReturnValue([], [e, test])
134+
135+
136+
def initialize(*args: T.List, **kwargs: T.Dict) -> RustModule:
137+
return RustModule(*args, **kwargs) # type: ignore

run_mypy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
'mesonbuild/mintro.py',
3030
'mesonbuild/mlog.py',
3131
'mesonbuild/modules/fs.py',
32+
'mesonbuild/modules/unstable_rust.py',
3233
'mesonbuild/mparser.py',
3334
'mesonbuild/msetup.py',
3435
'mesonbuild/mtest.py',

test cases/rust/9 unit tests/meson.build

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,14 @@ test(
3030
protocol : 'rust',
3131
suite : ['foo'],
3232
)
33+
34+
exe = executable('rust_exe', ['test2.rs', 'test.rs'])
35+
36+
rust = import('unstable-rust')
37+
rust.test('rust_test_from_exe', exe, should_fail : true)
38+
39+
lib = static_library('rust_static', ['test.rs'])
40+
rust.test('rust_test_from_static', lib, args: ['--skip', 'test_add_intentional_fail'])
41+
42+
lib = shared_library('rust_shared', ['test.rs'])
43+
rust.test('rust_test_from_shared', lib, args: ['--skip', 'test_add_intentional_fail'])

test cases/rust/9 unit tests/test2.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
mod test;
2+
use std::env;
3+
4+
fn main() {
5+
let args: Vec<String> = env::args().collect();
6+
let first = args[1].parse::<i32>().expect("Invliad value for first argument.");
7+
let second = args[2].parse::<i32>().expect("Invliad value for second argument.");
8+
9+
let new = test::add(first, second);
10+
println!("New value: {}", new);
11+
}

0 commit comments

Comments
 (0)