Skip to content

Commit

Permalink
Merge pull request mesonbuild#7860 from dcbaker/wip/2020-10/rust-module
Browse files Browse the repository at this point in the history
Add a rust module
  • Loading branch information
jpakkane authored Jan 6, 2021
2 parents f9dd75f + 827fa95 commit c9d9dac
Show file tree
Hide file tree
Showing 16 changed files with 364 additions and 51 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/mesonbuild/modules/pkgconfig.py @xclaesse
/mesonbuild/modules/cmake.py @mensinda
/mesonbuild/modules/unstable_external_project.py @xclaesse
/mesonbuild/modules/unstable_rust.py @dcbaker
/mesonbuild/ast/ @mensinda
/mesonbuild/cmake/ @mensinda
/mesonbuild/compilers/ @dcbaker
Expand Down
1 change: 1 addition & 0 deletions docs/markdown/Reference-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -1745,6 +1745,7 @@ test(..., env: nomalloc, ...)
to record the outcome of the test).
- `tap`: [Test Anything Protocol](https://www.testanything.org/).
- `gtest` *(since 0.55.0)*: for Google Tests.
- `rust` *(since 0.56.0)*: for native rust tests

- `priority` *(since 0.52.0)*:specifies the priority of a test. Tests with a
higher priority are *started* before tests with a lower priority.
Expand Down
35 changes: 35 additions & 0 deletions docs/markdown/Rust-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
short-description: Rust language integration module
authors:
- name: Dylan Baker
email: dylan@pnwbakers.com
years: [2020]
...

# Unstable Rust module

*(new in 0.57.0)*

**Note** Unstable modules make no backwards compatible API guarantees.

The rust module provides helper to integrate rust code into meson. The goal
is to make using rust in meson more pleasant, while still remaining mesonic,
this means that it attempts to make rust work more like meson, rather than
meson work more like rust.

## Functions

### test(name: string, target: library | executable, dependencies: []Dependency)

This function creates a new rust unittest target from an existing rust based
target, which may be a library or executable. It does this by copying the
sources and arguments passed to the original target and adding the `--test`
argument to the compilation, then creates a new test target which calls that
executable, using the rust test protocol.

This accepts all of the keyword arguments as the
[`test`](Reference-manual.md#test) function except `protocol`, it will set
that automatically.

Additional, test only dependencies may be passed via the dependencies
argument.
1 change: 1 addition & 0 deletions docs/markdown/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
* [gnome](Gnome-module.md)
* [i18n](i18n-module.md)
* [pkgconfig](Pkgconfig-module.md)
* [rust](Rust-module.md)
4 changes: 4 additions & 0 deletions docs/markdown/snippets/rust_test_format_support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Meson test() now accepts `protocol : 'rust'`

This allows native rust tests to be run and parsed by meson, simply set the
protocol to `rust` and meson takes care of the rest.
4 changes: 4 additions & 0 deletions docs/markdown/snippets/unstable-rust-module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Untable 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.
11 changes: 6 additions & 5 deletions docs/sitemap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,25 @@ index.md
Disabler.md
Modules.md
CMake-module.md
Cuda-module.md
Dlang-module.md
External-Project-module.md
Fs-module.md
Gnome-module.md
Hotdoc-module.md
i18n-module.md
Icestorm-module.md
Keyval-module.md
Pkgconfig-module.md
Python-module.md
Python-3-module.md
Python-module.md
Qt4-module.md
Qt5-module.md
RPM-module.md
Rust-module.md
Simd-module.md
SourceSet-module.md
Windows-module.md
Cuda-module.md
Keyval-module.md
External-Project-module.md
i18n-module.md
Java.md
Vala.md
D.md
Expand Down
45 changes: 23 additions & 22 deletions docs/theme/extra/templates/navbar_links.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,29 @@
Modules <span class="caret"></span>
</a>
<ul class="dropdown-menu" id="modules-menu">
@for tup in ( \
("CMake-module.html","CMake"), \
("Cuda-module.html","CUDA"), \
("Dlang-module.html","Dlang"), \
("Fs-module.html","Filesystem"), \
("Gnome-module.html","GNOME"), \
("Hotdoc-module.html","Hotdoc"), \
("i18n-module.html","i18n"), \
("Icestorm-module.html","Icestorm"), \
("Keyval-module.html","Keyval"), \
("Pkgconfig-module.html","Pkgconfig"), \
("Python-module.html","Python"), \
("Python-3-module.html","Python 3"), \
("Qt4-module.html","Qt4"), \
("Qt5-module.html","Qt5"), \
("RPM-module.html","RPM"), \
("SourceSet-module.html","SourceSet"), \
("Windows-module.html","Windows")):
<li>
<a href="@tup[0]">@tup[1]</a>
</li>
@end
@for tup in [ \
("CMake-module.html","CMake"), \
("Cuda-module.html","CUDA"), \
("Dlang-module.html","Dlang"), \
("Fs-module.html","Filesystem"), \
("Gnome-module.html","GNOME"), \
("Hotdoc-module.html","Hotdoc"), \
("Icestorm-module.html","Icestorm"), \
("Keyval-module.html","Keyval"), \
("Pkgconfig-module.html","Pkgconfig"), \
("Python-3-module.html","Python 3"), \
("Python-module.html","Python"), \
("Qt4-module.html","Qt4"), \
("Qt5-module.html","Qt5"), \
("RPM-module.html","RPM"), \
("Rust-module.html","Rust"), \
("SourceSet-module.html","SourceSet"), \
("Windows-module.html","Windows"), \
("i18n-module.html","i18n")]:
<li>
<a href="@tup[0]">@tup[1]</a>
</li>
@end
</ul>
</li>
\
Expand Down
5 changes: 5 additions & 0 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class TestProtocol(enum.Enum):
EXITCODE = 0
TAP = 1
GTEST = 2
RUST = 3

@classmethod
def from_str(cls, string: str) -> 'TestProtocol':
Expand All @@ -55,13 +56,17 @@ def from_str(cls, string: str) -> 'TestProtocol':
return cls.TAP
elif string == 'gtest':
return cls.GTEST
elif string == 'rust':
return cls.RUST
raise MesonException('unknown test format {}'.format(string))

def __str__(self) -> str:
if self is self.EXITCODE:
return 'exitcode'
elif self is self.GTEST:
return 'gtest'
elif self is self.RUST:
return 'rust'
return 'tap'


Expand Down
19 changes: 12 additions & 7 deletions mesonbuild/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2593,6 +2593,8 @@ def process_new_values(self, invalues):
self.process_new_values(v.sources[0])
elif isinstance(v, InstallDir):
self.build.install_dirs.append(v)
elif isinstance(v, Test):
self.build.tests.append(v)
elif hasattr(v, 'held_object'):
pass
elif isinstance(v, (int, str, bool, Disabler)):
Expand Down Expand Up @@ -4120,7 +4122,7 @@ def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables:
env = env.held_object
return env

def add_test(self, node, args, kwargs, is_base_test):
def make_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any]):
if len(args) != 2:
raise InterpreterException('test expects 2 arguments, {} given'.format(len(args)))
name = args[0]
Expand Down Expand Up @@ -4159,8 +4161,8 @@ def add_test(self, node, args, kwargs, is_base_test):
if not isinstance(timeout, int):
raise InterpreterException('Timeout must be an integer.')
protocol = kwargs.get('protocol', 'exitcode')
if protocol not in {'exitcode', 'tap', 'gtest'}:
raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".')
if protocol not in {'exitcode', 'tap', 'gtest', 'rust'}:
raise InterpreterException('Protocol must be one of "exitcode", "tap", "gtest", or "rust".')
suite = []
prj = self.subproject if self.is_subproject() else self.build.project_name
for s in mesonlib.stringlistify(kwargs.get('suite', '')):
Expand All @@ -4174,14 +4176,17 @@ def add_test(self, node, args, kwargs, is_base_test):
priority = kwargs.get('priority', 0)
if not isinstance(priority, int):
raise InterpreterException('Keyword argument priority must be an integer.')
t = Test(name, prj, suite, exe.held_object, depends, par, cmd_args,
env, should_fail, timeout, workdir, protocol, priority)
return Test(name, prj, suite, exe.held_object, depends, par, cmd_args,
env, should_fail, timeout, workdir, protocol, priority)

def add_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any], is_base_test: bool):
t = self.make_test(node, args, kwargs)
if is_base_test:
self.build.tests.append(t)
mlog.debug('Adding test', mlog.bold(name, True))
mlog.debug('Adding test', mlog.bold(t.name, True))
else:
self.build.benchmarks.append(t)
mlog.debug('Adding benchmark', mlog.bold(name, True))
mlog.debug('Adding benchmark', mlog.bold(t.name, True))

@FeatureNewKwargs('install_headers', '0.47.0', ['install_mode'])
@permittedKwargs(permitted_kwargs['install_headers'])
Expand Down
137 changes: 137 additions & 0 deletions mesonbuild/modules/unstable_rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Copyright © 2020 Intel Corporation

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import typing as T

from . import ExtensionModule, ModuleReturnValue
from .. import mlog
from ..build import BuildTarget, Executable, InvalidArguments
from ..dependencies import Dependency, ExternalLibrary
from ..interpreter import ExecutableHolder, permitted_kwargs
from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew
from ..mesonlib import stringlistify, unholder, listify

if T.TYPE_CHECKING:
from ..interpreter import ModuleState, Interpreter


class RustModule(ExtensionModule):

"""A module that holds helper functions for rust."""

@FeatureNew('rust module', '0.57.0')
def __init__(self, interpreter: 'Interpreter') -> None:
super().__init__(interpreter)

@permittedKwargs(permitted_kwargs['test'] | {'dependencies'} ^ {'protocol'})
def test(self, state: 'ModuleState', args: T.List, kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue:
"""Generate a rust test target from a given rust target.
Rust puts it's unitests inside it's main source files, unlike most
languages that put them in external files. This means that normally
you have to define two seperate targets with basically the same
arguments to get tests:
```meson
rust_lib_sources = [...]
rust_lib = static_library(
'rust_lib',
rust_lib_sources,
)
rust_lib_test = executable(
'rust_lib_test',
rust_lib_sources,
rust_args : ['--test'],
)
test(
'rust_lib_test',
rust_lib_test,
protocol : 'rust',
)
```
This is all fine, but not very DRY. This method makes it much easier
to define rust tests:
```meson
rust = import('unstable-rust')
rust_lib = static_library(
'rust_lib',
[sources],
)
rust.test('rust_lib_test', rust_lib)
```
"""
if len(args) != 2:
raise InterpreterException('rustmod.test() takes exactly 2 positional arguments')
name: str = args[0]
if not isinstance(name, str):
raise InterpreterException('First positional argument to rustmod.test() must be a string')
base_target: BuildTarget = unholder(args[1])
if not isinstance(base_target, BuildTarget):
raise InterpreterException('Second positional argument to rustmod.test() must be a library or executable')
if not base_target.get_using_rustc():
raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target')
extra_args = stringlistify(kwargs.get('args', []))

# Delete any arguments we don't want passed
if '--test' in extra_args:
mlog.warning('Do not add --test to rustmod.test arguments')
extra_args.remove('--test')
if '--format' in extra_args:
mlog.warning('Do not add --format to rustmod.test arguments')
i = extra_args.index('--format')
# Also delete the argument to --format
del extra_args[i + 1]
del extra_args[i]
for i, a in enumerate(extra_args):
if a.startswith('--format='):
del extra_args[i]
break

dependencies = unholder(listify(kwargs.get('dependencies', [])))
for d in dependencies:
if not isinstance(d, (Dependency, ExternalLibrary)):
raise InvalidArguments('dependencies must be a dependency or external library')

kwargs['args'] = extra_args + ['--test', '--format', 'pretty']
kwargs['protocol'] = 'rust'

new_target_kwargs = base_target.kwargs.copy()
# Don't mutate the shallow copied list, instead replace it with a new
# one
new_target_kwargs['rust_args'] = new_target_kwargs.get('rust_args', []) + ['--test']
new_target_kwargs['install'] = False
new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + dependencies

new_target = Executable(
name, base_target.subdir, state.subproject,
base_target.for_machine, base_target.sources,
base_target.objects, base_target.environment,
new_target_kwargs
)

e = ExecutableHolder(new_target, self.interpreter)
test = self.interpreter.make_test(
self.interpreter.current_node, [name, e], kwargs)

return ModuleReturnValue([], [e, test])


def initialize(*args: T.List, **kwargs: T.Dict) -> RustModule:
return RustModule(*args, **kwargs) # type: ignore
Loading

0 comments on commit c9d9dac

Please sign in to comment.