diff options
Diffstat (limited to 'mesonbuild/modules/rust.py')
-rw-r--r-- | mesonbuild/modules/rust.py | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py new file mode 100644 index 0000000..42401e4 --- /dev/null +++ b/mesonbuild/modules/rust.py @@ -0,0 +1,250 @@ +# Copyright © 2020-2022 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. +from __future__ import annotations + +import os +import typing as T + +from . import ExtensionModule, ModuleReturnValue, ModuleInfo +from .. import mlog +from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget, StructuredSources +from ..dependencies import Dependency, ExternalLibrary +from ..interpreter.type_checking import DEPENDENCIES_KW, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, include_dir_string_new +from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs +from ..mesonlib import File + +if T.TYPE_CHECKING: + from . import ModuleState + from ..interpreter import Interpreter + from ..interpreter import kwargs as _kwargs + from ..interpreter.interpreter import SourceInputs, SourceOutputs + from ..programs import ExternalProgram + + from typing_extensions import TypedDict + + class FuncTest(_kwargs.BaseTest): + + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] + is_parallel: bool + + class FuncBindgen(TypedDict): + + args: T.List[str] + c_args: T.List[str] + include_directories: T.List[IncludeDirs] + input: T.List[SourceInputs] + output: str + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] + + +class RustModule(ExtensionModule): + + """A module that holds helper functions for rust.""" + + INFO = ModuleInfo('rust', '0.57.0', stabilized='1.0.0') + + def __init__(self, interpreter: Interpreter) -> None: + super().__init__(interpreter) + self._bindgen_bin: T.Optional[ExternalProgram] = None + self.methods.update({ + 'test': self.test, + 'bindgen': self.bindgen, + }) + + @typed_pos_args('rust.test', str, BuildTarget) + @typed_kwargs( + 'rust.test', + *TEST_KWS, + DEPENDENCIES_KW, + KwargInfo('is_parallel', bool, default=False), + ) + def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> 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 separate 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) + ``` + """ + name = args[0] + base_target: BuildTarget = args[1] + if not base_target.uses_rust(): + raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target') + extra_args = kwargs['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 isinstance(a, str) and a.startswith('--format='): + del extra_args[i] + break + + # We need to cast here, as currently these don't have protocol in them, but test itself does. + tkwargs = T.cast('_kwargs.FuncTest', kwargs.copy()) + + tkwargs['args'] = extra_args + ['--test', '--format', 'pretty'] + tkwargs['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', []) + kwargs['dependencies'] + + sources = T.cast('T.List[SourceOutputs]', base_target.sources.copy()) + sources.extend(base_target.generated) + + new_target = Executable( + name, base_target.subdir, state.subproject, base_target.for_machine, + sources, base_target.structured_sources, + base_target.objects, base_target.environment, base_target.compilers, + new_target_kwargs + ) + + test = self.interpreter.make_test( + self.interpreter.current_node, (name, new_target), tkwargs) + + return ModuleReturnValue(None, [new_target, test]) + + @noPosargs + @typed_kwargs( + 'rust.bindgen', + KwargInfo('c_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo( + 'input', + ContainerTypeInfo(list, (File, GeneratedList, BuildTarget, BothLibraries, ExtractedObjects, CustomTargetIndex, CustomTarget, str), allow_empty=False), + default=[], + listify=True, + required=True, + ), + INCLUDE_DIRECTORIES.evolve(feature_validator=include_dir_string_new), + OUTPUT_KW, + DEPENDENCIES_KW.evolve(since='1.0.0'), + ) + def bindgen(self, state: ModuleState, args: T.List, kwargs: FuncBindgen) -> 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, *_deps = self.interpreter.source_strings_to_files(kwargs['input']) + + # Split File and Target dependencies to add pass to CustomTarget + depends: T.List[SourceOutputs] = [] + depend_files: T.List[File] = [] + for d in _deps: + if isinstance(d, File): + depend_files.append(d) + else: + depends.append(d) + + clang_args: T.List[str] = [] + for i in state.process_include_dirs(kwargs['include_directories']): + # bindgen always uses clang, so it's safe to hardcode -I here + clang_args.extend([f'-I{x}' for x in i.to_string_list( + state.environment.get_source_dir(), state.environment.get_build_dir())]) + + for de in kwargs['dependencies']: + for i in de.get_include_dirs(): + clang_args.extend([f'-I{x}' for x in i.to_string_list( + state.environment.get_source_dir(), state.environment.get_build_dir())]) + clang_args.extend(de.get_all_compile_args()) + for s in de.get_sources(): + if isinstance(s, File): + depend_files.append(s) + elif isinstance(s, CustomTarget): + depends.append(s) + + if self._bindgen_bin is None: + self._bindgen_bin = state.find_program('bindgen') + + name: str + if isinstance(header, File): + name = header.fname + elif isinstance(header, (BuildTarget, BothLibraries, ExtractedObjects, StructuredSources)): + raise InterpreterException('bindgen source file must be a C header, not an object or build target') + else: + name = header.get_outputs()[0] + + cmd = self._bindgen_bin.get_command() + \ + [ + '@INPUT@', '--output', + os.path.join(state.environment.build_dir, '@OUTPUT@') + ] + \ + kwargs['args'] + ['--'] + kwargs['c_args'] + clang_args + \ + ['-MD', '-MQ', '@INPUT@', '-MF', '@DEPFILE@'] + + target = CustomTarget( + f'rustmod-bindgen-{name}'.replace('/', '_'), + state.subdir, + state.subproject, + state.environment, + cmd, + [header], + [kwargs['output']], + depfile='@PLAINNAME@.d', + extra_depends=depends, + depend_files=depend_files, + backend=state.backend, + ) + + return ModuleReturnValue([target], [target]) + + +def initialize(interp: Interpreter) -> RustModule: + return RustModule(interp) |