From bb79d1ca857169bf95fea08667921f4e0744c9fd Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 Nov 2024 10:32:35 +0100 Subject: [PATCH 1/9] ninjabackend: split out generation of rustc arguments Allow reusing the code for doctests. In particular, the sources are shared between the two cases. Signed-off-by: Paolo Bonzini --- mesonbuild/backend/ninjabackend.py | 75 +++++++++++++++++++----------- mesonbuild/compilers/rust.py | 2 + 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 24758866c6b5..c2a8cebe0472 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1928,25 +1928,12 @@ def _get_rust_dependency_name(self, target: build.BuildTarget, dependency: LibTy # in Rust return target.rust_dependency_map.get(dependency.name, dependency.name).replace('-', '_') - def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = target.compilers['rust'] + def generate_rust_sources(self, target: build.BuildTarget) -> T.Tuple[T.List[str], str]: + orderdeps: T.List[str] = [] + # Rust compiler takes only the main file as input and # figures out what other files are needed via import # statements and magic. - base_proxy = target.get_options() - args = rustc.compiler_args() - # Compiler args for compiling this target - args += compilers.get_base_compile_args(base_proxy, rustc, self.environment) - self.generate_generator_list_rules(target) - - # dependencies need to cause a relink, they're not just for ordering - deps: T.List[str] = [] - - # Dependencies for rust-project.json - project_deps: T.List[RustDep] = [] - - orderdeps: T.List[str] = [] - main_rust_file = None if target.structured_sources: if target.structured_sources.needs_copy(): @@ -1976,14 +1963,10 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: orderdeps.extend(_ods) for i in target.get_sources(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') if main_rust_file is None: main_rust_file = i.rel_to_builddir(self.build_to_src) for g in target.get_generated_sources(): for i in g.get_outputs(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') if isinstance(g, GeneratedList): fname = os.path.join(self.get_target_private_dir(target), i) else: @@ -1991,26 +1974,36 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: if main_rust_file is None: main_rust_file = fname orderdeps.append(fname) - if main_rust_file is None: - raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + + return orderdeps, main_rust_file + + def get_rust_compiler_args(self, target: build.BuildTarget, rustc: Compiler, + depfile: T.Optional[str] = None) -> T.List[str]: + base_proxy = target.get_options() + # Compiler args for compiling this target + args = compilers.get_base_compile_args(base_proxy, rustc, self.environment) + target_name = self.get_target_filename(target) args.extend(['--crate-type', target.rust_crate_type]) # If we're dynamically linking, add those arguments - # - # Rust is super annoying, calling -C link-arg foo does not work, it has - # to be -C link-arg=foo if target.rust_crate_type in {'bin', 'dylib'}: args.extend(rustc.get_linker_always_args()) args += self.generate_basic_compiler_args(target, rustc) # Rustc replaces - with _. spaces or dots are not allowed, so we replace them with underscores args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_').replace('.', '_')] - depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') - args += rustc.get_dependency_gen_args(target_name, depfile) + if depfile: + args += rustc.get_dependency_gen_args(target_name, depfile) args += rustc.get_output_args(target_name) args += ['-C', 'metadata=' + target.get_id()] args += target.get_extra_args('rust') + return args + + def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler) -> T.Tuple[T.List[str], T.List[RustDep], T.List[str]]: + deps: T.List[str] = [] + project_deps: T.List[RustDep] = [] + args: T.List[str] = [] # Rustc always use non-debug Windows runtime. Inject the one selected # by Meson options instead. @@ -2124,6 +2117,33 @@ def _link_library(libname: str, static: bool, bundle: bool = False): if isinstance(target, build.SharedLibrary) or has_shared_deps: args += self.get_build_rpath_args(target, rustc) + return deps, project_deps, args + + def generate_rust_target(self, target: build.BuildTarget) -> None: + rustc = target.compilers['rust'] + self.generate_generator_list_rules(target) + + for i in target.get_sources(): + if not rustc.can_compile(i): + raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + for g in target.get_generated_sources(): + for i in g.get_outputs(): + if not rustc.can_compile(i): + raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + + orderdeps, main_rust_file = self.generate_rust_sources(target) + target_name = self.get_target_filename(target) + if main_rust_file is None: + raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + + args = rustc.compiler_args() + + depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') + args += self.get_rust_compiler_args(target, rustc, depfile) + + deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) + args += deps_args + proc_macro_dylib_path = None if target.rust_crate_type == 'proc-macro': proc_macro_dylib_path = self.get_target_filename_abs(target) @@ -2140,6 +2160,7 @@ def _link_library(libname: str, static: bool, bundle: bool = False): if orderdeps: element.add_orderdep(orderdeps) if deps: + # dependencies need to cause a relink, they're not just for ordering element.add_dep(deps) element.add_item('ARGS', args) element.add_item('targetdep', depfile) diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index b8588b8d7db6..878b6d1419d0 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -270,6 +270,8 @@ def get_colorout_args(self, colortype: str) -> T.List[str]: def get_linker_always_args(self) -> T.List[str]: args: T.List[str] = [] + # Rust is super annoying, calling -C link-arg foo does not work, it has + # to be -C link-arg=foo for a in super().get_linker_always_args(): args.extend(['-C', f'link-arg={a}']) return args From b945bddb44be50f610799c7783257bc64f3849b0 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 21 Nov 2024 09:50:36 +0100 Subject: [PATCH 2/9] compilers: introduce get_exe() and get_exe_args() This will be used by rustdoc tests because the Test objects takes a single string for the command and everything else goes in the args. But apart from this, the need to split the executable from the arguments is common so create new methods to do it. While at it, fix brokenness in the handling of the zig compiler, which is checking against "zig" but failing to detect e.g. "/usr/bin/zig". Signed-off-by: Paolo Bonzini --- mesonbuild/backend/backends.py | 5 ++--- mesonbuild/cmake/toolchain.py | 3 ++- mesonbuild/compilers/compilers.py | 6 ++++++ mesonbuild/compilers/detect.py | 4 ++-- mesonbuild/compilers/rust.py | 2 +- mesonbuild/dependencies/dev.py | 2 +- mesonbuild/interpreter/interpreter.py | 7 +++---- mesonbuild/linkers/linkers.py | 6 ++++++ 8 files changed, 23 insertions(+), 12 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 8d9796db95c6..a789f2824905 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -2079,9 +2079,8 @@ def compiler_to_generator(self, target: build.BuildTarget, Some backends don't support custom compilers. This is a convenience method to convert a Compiler to a Generator. ''' - exelist = compiler.get_exelist() - exe = programs.ExternalProgram(exelist[0]) - args = exelist[1:] + exe = programs.ExternalProgram(compiler.get_exe()) + args = compiler.get_exe_args() commands = self.compiler_to_generator_args(target, compiler) generator = build.Generator(exe, args + commands.to_native(), [output_templ], depfile='@PLAINNAME@.d', diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py index 9eb961c52bc7..d410886ecf25 100644 --- a/mesonbuild/cmake/toolchain.py +++ b/mesonbuild/cmake/toolchain.py @@ -9,6 +9,7 @@ from .common import language_map, cmake_get_generator_args from .. import mlog +import os.path import shutil import typing as T from enum import Enum @@ -198,7 +199,7 @@ def is_cmdline_option(compiler: 'Compiler', arg: str) -> bool: if compiler.get_argument_syntax() == 'msvc': return arg.startswith('/') else: - if compiler.exelist[0] == 'zig' and arg in {'ar', 'cc', 'c++', 'dlltool', 'lib', 'ranlib', 'objcopy', 'rc'}: + if os.path.basename(compiler.get_exe()) == 'zig' and arg in {'ar', 'cc', 'c++', 'dlltool', 'lib', 'ranlib', 'objcopy', 'rc'}: return True return arg.startswith('-') diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 424bcc19bf6a..f360569da637 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -488,6 +488,12 @@ def get_id(self) -> str: def get_modes(self) -> T.List[Compiler]: return self.modes + def get_exe(self) -> str: + return self.exelist[0] + + def get_exe_args(self) -> T.List[str]: + return self.exelist[1:] + def get_linker_id(self) -> str: # There is not guarantee that we have a dynamic linker instance, as # some languages don't have separate linkers and compilers. In those diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index d4ad4badeefd..de682d21e0d0 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -1094,7 +1094,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust extra_args: T.Dict[str, T.Union[str, bool]] = {} always_args: T.List[str] = [] if is_link_exe: - compiler.extend(cls.use_linker_args(cc.linker.exelist[0], '')) + compiler.extend(cls.use_linker_args(cc.linker.get_exe(), '')) extra_args['direct'] = True extra_args['machine'] = cc.linker.machine else: @@ -1126,7 +1126,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust # inserts the correct prefix itself. assert isinstance(linker, linkers.VisualStudioLikeLinkerMixin) linker.direct = True - compiler.extend(cls.use_linker_args(linker.exelist[0], '')) + compiler.extend(cls.use_linker_args(linker.get_exe(), '')) else: # On linux and macos rust will invoke the c compiler for # linking, on windows it will use lld-link or link.exe. diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 878b6d1419d0..ba6bd63946db 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -305,7 +305,7 @@ def get_rust_tool(self, name: str, env: Environment) -> T.List[str]: exelist = rustup_exelist + [name] else: exelist = [name] - args = self.exelist[1:] + args = self.get_exe_args() from ..programs import find_external_program for prog in find_external_program(env, self.for_machine, exelist[0], exelist[0], diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 2725a7bb4e88..9aed1ae2f4a7 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -591,7 +591,7 @@ def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): self.java_home = environment.properties[self.for_machine].get_java_home() if not self.java_home: - self.java_home = pathlib.Path(shutil.which(self.javac.exelist[0])).resolve().parents[1] + self.java_home = pathlib.Path(shutil.which(self.javac.get_exe())).resolve().parents[1] if m.is_darwin(): problem_java_prefix = pathlib.Path('/System/Library/Frameworks/JavaVM.framework/Versions') if problem_java_prefix in self.java_home.parents: diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 66ea24cf3468..cdf8321a8d8d 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -806,13 +806,12 @@ def run_command_impl(self, if not cmd.found(): raise InterpreterException(f'command {cmd.get_name()!r} not found or not executable') elif isinstance(cmd, compilers.Compiler): - exelist = cmd.get_exelist() - cmd = exelist[0] + expanded_args = cmd.get_exe_args() + cmd = cmd.get_exe() prog = ExternalProgram(cmd, silent=True) if not prog.found(): raise InterpreterException(f'Program {cmd!r} not found or not executable') cmd = prog - expanded_args = exelist[1:] else: if isinstance(cmd, mesonlib.File): cmd = cmd.absolute_path(srcdir, builddir) @@ -831,7 +830,7 @@ def run_command_impl(self, expanded_args.append(a.get_path()) elif isinstance(a, compilers.Compiler): FeatureNew.single_use('Compiler object as a variadic argument to `run_command`', '0.61.0', self.subproject, location=self.current_node) - prog = ExternalProgram(a.exelist[0], silent=True) + prog = ExternalProgram(a.get_exe(), silent=True) if not prog.found(): raise InterpreterException(f'Program {cmd!r} not found or not executable') expanded_args.append(prog.get_path()) diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index 176fb3348204..11a5f257877e 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -29,6 +29,9 @@ def __init__(self, exelist: T.List[str]): def get_id(self) -> str: return self.id + def get_exe(self) -> str: + return self.exelist[0] + def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CompilerArgs: return CompilerArgs(self, args) @@ -147,6 +150,9 @@ def __repr__(self) -> str: def get_id(self) -> str: return self.id + def get_exe(self) -> str: + return self.exelist[0] + def get_version_string(self) -> str: return f'({self.id} {self.version})' From 8b260785e463f40633a7f5e39cae5a86aa2dedb3 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 Nov 2024 12:10:37 +0100 Subject: [PATCH 3/9] build, interpreter: associate a doctest target to a BuildTarget A doctest target is a separate build target (with its own linker arguments, including dependencies) that is built and added as a unit test whenever the parent target is built. The doctest's target is not accessible via ninja. Signed-off-by: Paolo Bonzini --- mesonbuild/build.py | 3 +- mesonbuild/interpreter/interpreter.py | 32 +++++++++++--------- mesonbuild/interpreter/interpreterobjects.py | 5 +++ mesonbuild/modules/rust.py | 3 +- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c72857d2c1ae..5171d4b4641a 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -43,7 +43,7 @@ from .backend.backends import Backend from .compilers import Compiler from .interpreter.interpreter import SourceOutputs, Interpreter - from .interpreter.interpreterobjects import Test + from .interpreter.interpreterobjects import Test, Doctest from .interpreterbase import SubProject from .linkers.linkers import StaticLinker from .mesonlib import ExecutableSerialisation, FileMode, FileOrString @@ -755,6 +755,7 @@ def __init__( self.name_prefix_set = False self.name_suffix_set = False self.filename = 'no_name' + self.doctests: T.Optional[Doctest] = None # The debugging information file this target will generate self.debug_filename = None # The list of all files outputted by this target. Useful in cases such diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index cdf8321a8d8d..7232669db681 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -131,6 +131,7 @@ ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str] + TestClass = T.TypeVar('TestClass', bound=Test) def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) -> T.Optional[str]: if isinstance(value, list): @@ -2263,7 +2264,8 @@ def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariables, T.Dict[str, 'TY def make_test(self, node: mparser.BaseNode, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], - kwargs: 'kwtypes.BaseTest') -> Test: + kwargs: 'kwtypes.BaseTest', + klass: T.Type[TestClass] = Test) -> TestClass: name = args[0] if ':' in name: mlog.deprecation(f'":" is not allowed in test name "{name}", it has been replaced with "_"', @@ -2293,20 +2295,20 @@ def make_test(self, node: mparser.BaseNode, s = ':' + s suite.append(prj.replace(' ', '_').replace(':', '_') + s) - return Test(name, - prj, - suite, - exe, - kwargs['depends'], - kwargs.get('is_parallel', False), - kwargs['args'], - env, - kwargs['should_fail'], - kwargs['timeout'], - kwargs['workdir'], - kwargs['protocol'], - kwargs['priority'], - kwargs['verbose']) + return klass(name, + prj, + suite, + exe, + kwargs['depends'], + kwargs.get('is_parallel', False), + kwargs['args'], + env, + kwargs['should_fail'], + kwargs['timeout'], + kwargs['workdir'], + kwargs['protocol'], + kwargs['priority'], + kwargs['verbose']) def add_test(self, node: mparser.BaseNode, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index f4a2b4107ed3..1751008deddf 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -809,6 +809,11 @@ def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarg def get_name(self) -> str: return self.name + +class Doctest(Test): + target: T.Optional[build.BuildTarget] = None + + class NullSubprojectInterpreter(HoldableObject): pass diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 5072e503ec9e..87e07dfc2516 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -28,6 +28,7 @@ from ..interpreter import Interpreter from ..interpreter import kwargs as _kwargs from ..interpreter.interpreter import SourceInputs, SourceOutputs + from ..interpreter.interpreterobjects import Test from ..programs import OverrideProgram from ..interpreter.type_checking import SourcesVarargsType @@ -184,7 +185,7 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func new_target_kwargs ) - test = self.interpreter.make_test( + test: Test = self.interpreter.make_test( self.interpreter.current_node, (name, new_target), tkwargs) return ModuleReturnValue(None, [new_target, test]) From b422755f9a8c328b606946bdd4808e04e66ba81b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 Nov 2024 14:42:29 +0100 Subject: [PATCH 4/9] ninjabackend: generate command line for rust doctests Adjust get_rust_compiler_args() to accept the crate-type externally, because rustdoc tests are an executable but are compiled with the parent target's --crate-type. Apart from that, the rustdoc arguments are very similar to the parent target, and are handled by the same functions that were split out of generate_rust_target. This concludes the backend implementation of doctests, only leaving the implementation of a doctest() function in the rust module. Signed-off-by: Paolo Bonzini --- mesonbuild/backend/ninjabackend.py | 18 ++++++++++++++---- mesonbuild/compilers/rust.py | 10 +++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index c2a8cebe0472..c2b8017e3e84 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -45,6 +45,7 @@ from ..linkers.linkers import DynamicLinker, StaticLinker from ..compilers.cs import CsCompiler from ..compilers.fortran import FortranCompiler + from ..compilers.rust import RustCompiler from ..mesonlib import FileOrString from .backends import TargetIntrospectionData @@ -1977,14 +1978,14 @@ def generate_rust_sources(self, target: build.BuildTarget) -> T.Tuple[T.List[str return orderdeps, main_rust_file - def get_rust_compiler_args(self, target: build.BuildTarget, rustc: Compiler, + def get_rust_compiler_args(self, target: build.BuildTarget, rustc: Compiler, src_crate_type: str, depfile: T.Optional[str] = None) -> T.List[str]: base_proxy = target.get_options() # Compiler args for compiling this target args = compilers.get_base_compile_args(base_proxy, rustc, self.environment) target_name = self.get_target_filename(target) - args.extend(['--crate-type', target.rust_crate_type]) + args.extend(['--crate-type', src_crate_type]) # If we're dynamically linking, add those arguments if target.rust_crate_type in {'bin', 'dylib'}: @@ -2120,7 +2121,7 @@ def _link_library(libname: str, static: bool, bundle: bool = False): return deps, project_deps, args def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = target.compilers['rust'] + rustc = T.cast('RustCompiler', target.compilers['rust']) self.generate_generator_list_rules(target) for i in target.get_sources(): @@ -2139,7 +2140,7 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: args = rustc.compiler_args() depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') - args += self.get_rust_compiler_args(target, rustc, depfile) + args += self.get_rust_compiler_args(target, rustc, target.rust_crate_type, depfile) deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) args += deps_args @@ -2169,6 +2170,15 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: self.generate_shsym(target) self.create_target_source_introspection(target, rustc, args, [main_rust_file], []) + if target.doctests: + assert target.doctests.target is not None + rustdoc = rustc.get_rustdoc(self.environment) + args = rustdoc.get_exe_args() + args += self.get_rust_compiler_args(target.doctests.target, rustdoc, target.rust_crate_type) + _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc) + args += deps_args + target.doctests.cmd_args = args.to_native() + [main_rust_file] + target.doctests.cmd_args + @staticmethod def get_rule_suffix(for_machine: MachineChoice) -> str: return PerMachine('_FOR_BUILD', '')[for_machine] diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index ba6bd63946db..6e005e78f1df 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -103,7 +103,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def needs_static_linker(self) -> bool: return False - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str, environment: Environment) -> None: source_name = os.path.join(work_dir, 'sanity.rs') output_name = os.path.join(work_dir, 'rusttest') cmdlist = self.exelist.copy() @@ -317,6 +317,14 @@ def get_rust_tool(self, name: str, env: Environment) -> T.List[str]: return exelist + args + @functools.lru_cache(maxsize=None) + def get_rustdoc(self, env: 'Environment') -> T.Optional[RustdocTestCompiler]: + exelist = self.get_rust_tool('rustdoc', env) + if not exelist: + return None + + return RustdocTestCompiler(exelist, self.version, self.for_machine, + self.is_cross, self.info, linker=self.linker) class ClippyRustCompiler(RustCompiler): From b843071ca19f26c68da209d6a412629bf8118a4f Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 21 Nov 2024 10:05:16 +0100 Subject: [PATCH 5/9] interpreter, rust: move "args" out of BaseTest rust.doctest() will have to typecheck that to a list of strings, no other argument types are allowed. Extract the field out of BaseTest, placing it in FuncBenchmark and the rust modules's FuncTest. Signed-off-by: Paolo Bonzini --- mesonbuild/interpreter/kwargs.py | 5 ++++- mesonbuild/interpreter/type_checking.py | 9 ++++++--- mesonbuild/modules/rust.py | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 87f121e90b0f..e021d59e8653 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -19,6 +19,9 @@ from ..programs import ExternalProgram from .type_checking import PkgConfigDefineType, SourcesVarargsType +if T.TYPE_CHECKING: + TestArgs = T.Union[str, File, build.Target, ExternalProgram] + class FuncAddProjectArgs(TypedDict): """Keyword Arguments for the add_*_arguments family of arguments. @@ -38,7 +41,6 @@ class BaseTest(TypedDict): """Shared base for the Rust module.""" - args: T.List[T.Union[str, File, build.Target, ExternalProgram]] should_fail: bool timeout: int workdir: T.Optional[str] @@ -52,6 +54,7 @@ class FuncBenchmark(BaseTest): """Keyword Arguments shared between `test` and `benchmark`.""" + args: T.List[TestArgs] protocol: Literal['exitcode', 'tap', 'gtest', 'rust'] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index ed34be950065..7519945cfbd8 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -484,9 +484,7 @@ def link_whole_validator(values: T.List[T.Union[StaticLibrary, CustomTarget, Cus PRESERVE_PATH_KW: KwargInfo[bool] = KwargInfo('preserve_path', bool, default=False, since='0.63.0') -TEST_KWS: T.List[KwargInfo] = [ - KwargInfo('args', ContainerTypeInfo(list, (str, File, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram)), - listify=True, default=[]), +TEST_KWS_NO_ARGS: T.List[KwargInfo] = [ KwargInfo('should_fail', bool, default=False), KwargInfo('timeout', int, default=30), KwargInfo('workdir', (str, NoneType), default=None, @@ -503,6 +501,11 @@ def link_whole_validator(values: T.List[T.Union[StaticLibrary, CustomTarget, Cus KwargInfo('verbose', bool, default=False, since='0.62.0'), ] +TEST_KWS: T.List[KwargInfo] = TEST_KWS_NO_ARGS + [ + KwargInfo('args', ContainerTypeInfo(list, (str, File, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram)), + listify=True, default=[]), +] + # Cannot have a default value because we need to check that rust_crate_type and # rust_abi are mutually exclusive. RUST_CRATE_TYPE_KW: KwargInfo[T.Union[str, None]] = KwargInfo( diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 87e07dfc2516..9e0a5af3fd27 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -36,6 +36,7 @@ class FuncTest(_kwargs.BaseTest): + args: T.List[_kwargs.TestArgs] dependencies: T.List[T.Union[Dependency, ExternalLibrary]] is_parallel: bool link_with: T.List[LibTypes] From 8fb209025b9d5a693dde455c9f377ff2a462ba08 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 19 Dec 2024 23:01:25 +0100 Subject: [PATCH 6/9] rust: unit tests: do not use deprecated rust_crate_type Signed-off-by: Paolo Bonzini --- test cases/rust/9 unit tests/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index aa9da679693e..4d04ee892213 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -49,7 +49,7 @@ exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false) rust = import('rust') rust.test('rust_test_from_exe', exe, should_fail : true) -lib = static_library('rust_static', ['test.rs'], build_by_default : false, rust_crate_type : 'lib') +lib = static_library('rust_static', ['test.rs'], build_by_default : false, rust_abi: 'c') rust.test('rust_test_from_static', lib, args: ['--skip', 'test_add_intentional_fail']) lib = shared_library('rust_shared', ['test.rs'], build_by_default : false) From 9ed424fe820ebf8d8dadb73f1b88c451764fce93 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 Nov 2024 14:33:15 +0100 Subject: [PATCH 7/9] rust: extract common parts of rust.test and rust.doctest Signed-off-by: Paolo Bonzini --- mesonbuild/modules/rust.py | 54 +++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 9e0a5af3fd27..6d0a4ac9f2cc 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -34,14 +34,17 @@ from typing_extensions import TypedDict, Literal - class FuncTest(_kwargs.BaseTest): + ArgsType = T.TypeVar('ArgsType') - args: T.List[_kwargs.TestArgs] + class FuncRustTest(_kwargs.BaseTest, T.Generic[ArgsType]): + args: T.List[ArgsType] dependencies: T.List[T.Union[Dependency, ExternalLibrary]] is_parallel: bool link_with: T.List[LibTypes] rust_args: T.List[str] + FuncTest = FuncRustTest[_kwargs.TestArgs] + class FuncBindgen(TypedDict): args: T.List[str] @@ -55,6 +58,18 @@ class FuncBindgen(TypedDict): bindgen_version: T.List[str] +RUST_TEST_KWS: T.List[KwargInfo] = [ + KwargInfo( + 'rust_args', + ContainerTypeInfo(list, str), + listify=True, + default=[], + since='1.2.0', + ), + KwargInfo('is_parallel', bool, default=False), +] + + class RustModule(ExtensionModule): """A module that holds helper functions for rust.""" @@ -74,22 +89,7 @@ def __init__(self, interpreter: Interpreter) -> None: 'proc_macro': self.proc_macro, }) - @typed_pos_args('rust.test', str, BuildTarget) - @typed_kwargs( - 'rust.test', - *TEST_KWS, - DEPENDENCIES_KW, - LINK_WITH_KW.evolve(since='1.2.0'), - KwargInfo( - 'rust_args', - ContainerTypeInfo(list, str), - listify=True, - default=[], - since='1.2.0', - ), - KwargInfo('is_parallel', bool, default=False), - ) - def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> ModuleReturnValue: + def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncRustTest) -> T.Tuple[Executable, _kwargs.FuncTest]: """Generate a rust test target from a given rust target. Rust puts its unitests inside its main source files, unlike most @@ -137,15 +137,15 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func 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') + raise InterpreterException(f'Second positional argument to rustmod.{funcname}() 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') + mlog.warning(f'Do not add --test to rustmod.{funcname}() arguments') extra_args.remove('--test') if '--format' in extra_args: - mlog.warning('Do not add --format to rustmod.test arguments') + mlog.warning(f'Do not add --format to rustmod.{funcname}() arguments') i = extra_args.index('--format') # Also delete the argument to --format del extra_args[i + 1] @@ -185,7 +185,19 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func base_target.objects, base_target.environment, base_target.compilers, new_target_kwargs ) + return new_target, tkwargs + @typed_pos_args('rust.test', str, BuildTarget) + @typed_kwargs( + 'rust.test', + *TEST_KWS, + DEPENDENCIES_KW, + LINK_WITH_KW.evolve(since='1.2.0'), + *RUST_TEST_KWS, + ) + def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> ModuleReturnValue: + name, _ = args + new_target, tkwargs = self.test_common('test', state, args, kwargs) test: Test = self.interpreter.make_test( self.interpreter.current_node, (name, new_target), tkwargs) From 39aa8179c27d67492cff48759520d56b4c7c3987 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 Nov 2024 11:24:08 +0100 Subject: [PATCH 8/9] rust: add rust.doctest Signed-off-by: Paolo Bonzini --- docs/markdown/Rust-module.md | 27 ++++++++ mesonbuild/modules/rust.py | 81 ++++++++++++++++++++++-- test cases/rust/9 unit tests/doctest1.rs | 2 +- test cases/rust/9 unit tests/meson.build | 13 ++-- 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index ee095e9d6da1..6e8202a6fe16 100644 --- a/docs/markdown/Rust-module.md +++ b/docs/markdown/Rust-module.md @@ -24,6 +24,8 @@ like Meson, rather than Meson work more like rust. rustmod.test(name, target, ...) ``` +*Since 1.8.0* + 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 @@ -41,6 +43,31 @@ It also takes the following keyword arguments: This function also accepts all of the keyword arguments accepted by the [[test]] function except `protocol`, it will set that automatically. +### doctest() + +```meson +rustmod.doctest(name, target, ...) +``` + +This function creates a new `test()` target from an existing rust +based library target. The test will use `rustdoc` to extract and run +the doctests that are included in `target`'s sources. + +This function takes two positional arguments, the first is the name of the +test and the second is the library or executable that is the rust based target. +It also takes the following keyword arguments: + +- `dependencies`: a list of test-only Dependencies +- `link_with`: a list of additional build Targets to link with +- `rust_args`: a list of extra arguments passed to the Rust compiler + +The target is linked automatically into the doctests. + +This function also accepts all of the keyword arguments accepted by the +[[test]] function except `protocol`, it will set that automatically. +However, arguments are limited to strings that do not contain spaces +due to limitations of `rustdoc`. + ### bindgen() This function wraps bindgen to simplify creating rust bindings around C diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 6d0a4ac9f2cc..21c94bd0ff43 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -4,6 +4,7 @@ from __future__ import annotations import itertools import os +import re import typing as T from mesonbuild.interpreterbase.decorators import FeatureNew @@ -11,15 +12,17 @@ from . import ExtensionModule, ModuleReturnValue, ModuleInfo from .. import mesonlib, mlog from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, - CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary) + CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary, StaticLibrary) from ..compilers.compilers import are_asserts_disabled, lang_suffixes +from ..compilers.rust import RustCompiler from ..interpreter.type_checking import ( - DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, OUTPUT_KW, + DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator ) from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs -from ..mesonlib import File -from ..programs import ExternalProgram +from ..interpreter.interpreterobjects import Doctest +from ..mesonlib import File, MesonException, PerMachine +from ..programs import ExternalProgram, NonExistingExternalProgram if T.TYPE_CHECKING: from . import ModuleState @@ -44,6 +47,7 @@ class FuncRustTest(_kwargs.BaseTest, T.Generic[ArgsType]): rust_args: T.List[str] FuncTest = FuncRustTest[_kwargs.TestArgs] + FuncDoctest = FuncRustTest[str] class FuncBindgen(TypedDict): @@ -69,12 +73,18 @@ class FuncBindgen(TypedDict): KwargInfo('is_parallel', bool, default=False), ] +def no_spaces_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str]: + if any(bool(re.search(r'\s', x)) for x in arg): + return 'must not contain spaces due to limitations of rustdoc' + return None + class RustModule(ExtensionModule): """A module that holds helper functions for rust.""" INFO = ModuleInfo('rust', '0.57.0', stabilized='1.0.0') + rustdoc: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None) def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) @@ -85,6 +95,7 @@ def __init__(self, interpreter: Interpreter) -> None: self._bindgen_rust_target = None self.methods.update({ 'test': self.test, + 'doctest': self.doctest, 'bindgen': self.bindgen, 'proc_macro': self.proc_macro, }) @@ -203,6 +214,68 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func return ModuleReturnValue(None, [new_target, test]) + @FeatureNew('rust.doctest', '1.8.0') + @typed_pos_args('rust.doctest', str, BuildTarget) + @typed_kwargs( + 'rust.doctest', + *TEST_KWS_NO_ARGS, + DEPENDENCIES_KW, + LINK_WITH_KW, + *RUST_TEST_KWS, + KwargInfo( + 'args', + ContainerTypeInfo(list, str), + listify=True, + default=[], + validator=no_spaces_validator, + ), + ) + def doctest(self, state: ModuleState, args: T.Tuple[str, T.Union[SharedLibrary, StaticLibrary]], kwargs: FuncDoctest) -> ModuleReturnValue: + name, base_target = args + + # Link the base target's crate into the tests + kwargs['link_with'].append(base_target) + kwargs['depends'].append(base_target) + workdir = kwargs['workdir'] + kwargs['workdir'] = None + new_target, tkwargs = self.test_common('doctest', state, args, kwargs) + + # added automatically by rustdoc; keep things simple + tkwargs['args'].remove('--test') + + # --test-args= is "parsed" simply via the Rust function split_whitespace(). + # This means no quoting nightmares (pfew) but it also means no spaces. + # Unfortunately it's pretty hard at this point to accept e.g. CustomTarget, + # because their paths may not be known. This is not a big deal because the + # user does not control the test harness, so make things easy and allow + # strings only. + if tkwargs['args']: + tkwargs['args'] = ['--test-args=' + ' '.join(T.cast('T.Sequence[str]', tkwargs['args']))] + if workdir: + tkwargs['args'].append('--test-run-directory=' + workdir) + + if self.rustdoc[base_target.for_machine] is None: + rustc = base_target.compilers['rust'] + assert isinstance(rustc, RustCompiler) + rustdoc = rustc.get_rustdoc(state.environment) + if rustdoc: + self.rustdoc[base_target.for_machine] = ExternalProgram(rustdoc.get_exe()) + else: + self.rustdoc[base_target.for_machine] = NonExistingExternalProgram() + + rustdoc_prog = self.rustdoc[base_target.for_machine] + if not rustdoc_prog.found(): + raise MesonException(f'could not find rustdoc for {base_target.for_machine} machine') + + doctests: Doctest = self.interpreter.make_test( + self.interpreter.current_node, (name, rustdoc_prog), tkwargs, Doctest) + + # Note that the new_target is intentionally not returned, as it + # is only reached via the base_target and never built by "ninja" + doctests.target = new_target + base_target.doctests = doctests + return ModuleReturnValue(None, [doctests]) + @noPosargs @typed_kwargs( 'rust.bindgen', diff --git a/test cases/rust/9 unit tests/doctest1.rs b/test cases/rust/9 unit tests/doctest1.rs index d270f7d67047..da42792b8be0 100644 --- a/test cases/rust/9 unit tests/doctest1.rs +++ b/test cases/rust/9 unit tests/doctest1.rs @@ -7,6 +7,6 @@ /// ```ignore /// this one will be skipped /// ``` -fn my_func() +pub fn my_func() { } diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index 4d04ee892213..0fa2fa80b304 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -1,4 +1,4 @@ -project('rust unit tests', 'rust', meson_version: '>=1.2.0') +project('rust unit tests', 'rust', meson_version: '>=1.8.0') t = executable( 'rust_test', @@ -31,14 +31,12 @@ test( suite : ['foo'], ) +rust = import('rust') + rustdoc = find_program('rustdoc', required: false) if rustdoc.found() - # rustdoc is invoked mostly like rustc. This is a simple example - # where it is easy enough to invoke it by hand. - test( - 'rust doctest', - rustdoc, - args : ['--test', '--crate-name', 'doctest1', '--crate-type', 'lib', files('doctest1.rs')], + doclib = static_library('rust_doc_lib', ['doctest1.rs'], build_by_default : false) + rust.doctest('rust doctests', doclib, protocol : 'rust', suite : ['doctests'], ) @@ -46,7 +44,6 @@ endif exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false) -rust = import('rust') rust.test('rust_test_from_exe', exe, should_fail : true) lib = static_library('rust_static', ['test.rs'], build_by_default : false, rust_abi: 'c') From b430d7618c2b418cb6732df8fdb8651e5449fd4a Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 19 Dec 2024 23:38:31 +0100 Subject: [PATCH 9/9] rust: add link_whole to rust.test and rust.doctest QEMU needs it in its integration tests (in order to run global constructors), and therefore in rust.doctest too. With this change I could do: # Rust executables do not support objects, so add an intermediate step. rust_qemu_api_objs = static_library( 'rust_qemu_api_objs', objects: [libqom.extract_all_objects(recursive: false), libhwcore.extract_all_objects(recursive: false)]) rust.doctest('rust-qemu-api-doc', _qemu_api_rs, dependencies: [qemu_api, qemu_api_macros], link_with: libqemuutil, link_whole: [rust_qemu_api_objs], suite: ['doc', 'rust']) followed by "meson test --suite doc". For completeness, add it to rust.test as well. Signed-off-by: Paolo Bonzini --- docs/markdown/Rust-module.md | 2 ++ docs/markdown/snippets/rust-test-link-whole.md | 4 ++++ mesonbuild/modules/rust.py | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 docs/markdown/snippets/rust-test-link-whole.md diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index 6e8202a6fe16..d2c478c64239 100644 --- a/docs/markdown/Rust-module.md +++ b/docs/markdown/Rust-module.md @@ -38,6 +38,7 @@ It also takes the following keyword arguments: - `dependencies`: a list of test-only Dependencies - `link_with`: a list of additional build Targets to link with (*since 1.2.0*) +- `link_whole`: a list of additional build Targets to link with in their entirety (*since 1.8.0*) - `rust_args`: a list of extra arguments passed to the Rust compiler (*since 1.2.0*) This function also accepts all of the keyword arguments accepted by the @@ -59,6 +60,7 @@ It also takes the following keyword arguments: - `dependencies`: a list of test-only Dependencies - `link_with`: a list of additional build Targets to link with +- `link_whole`: a list of additional build Targets to link with in their entirety - `rust_args`: a list of extra arguments passed to the Rust compiler The target is linked automatically into the doctests. diff --git a/docs/markdown/snippets/rust-test-link-whole.md b/docs/markdown/snippets/rust-test-link-whole.md new file mode 100644 index 000000000000..f3d006d5389d --- /dev/null +++ b/docs/markdown/snippets/rust-test-link-whole.md @@ -0,0 +1,4 @@ +## `rust.test` now supports `link_whole` + +The `test` function in the `rust` module now supports the `link_whole` +keyword argument in addition to `link_with` and `dependencies`. diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 21c94bd0ff43..bf55d36194f3 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -16,8 +16,8 @@ from ..compilers.compilers import are_asserts_disabled, lang_suffixes from ..compilers.rust import RustCompiler from ..interpreter.type_checking import ( - DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS, OUTPUT_KW, - INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator + DEPENDENCIES_KW, LINK_WITH_KW, LINK_WHOLE_KW, SHARED_LIB_KWS, TEST_KWS, TEST_KWS_NO_ARGS, + OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS, NoneType, in_set_validator ) from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs from ..interpreter.interpreterobjects import Doctest @@ -44,6 +44,7 @@ class FuncRustTest(_kwargs.BaseTest, T.Generic[ArgsType]): dependencies: T.List[T.Union[Dependency, ExternalLibrary]] is_parallel: bool link_with: T.List[LibTypes] + link_whole: T.List[LibTypes] rust_args: T.List[str] FuncTest = FuncRustTest[_kwargs.TestArgs] @@ -144,6 +145,8 @@ def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, Buil """ if any(isinstance(t, Jar) for t in kwargs.get('link_with', [])): raise InvalidArguments('Rust tests cannot link with Jar targets') + if any(isinstance(t, Jar) for t in kwargs.get('link_whole', [])): + raise InvalidArguments('Rust tests cannot link with Jar targets') name = args[0] base_target: BuildTarget = args[1] @@ -178,6 +181,7 @@ def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, Buil new_target_kwargs['install'] = False new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + kwargs['dependencies'] new_target_kwargs['link_with'] = new_target_kwargs.get('link_with', []) + kwargs['link_with'] + new_target_kwargs['link_whole'] = new_target_kwargs.get('link_whole', []) + kwargs['link_whole'] del new_target_kwargs['rust_crate_type'] for kw in ['pic', 'prelink', 'rust_abi', 'version', 'soversion', 'darwin_versions']: if kw in new_target_kwargs: @@ -204,6 +208,7 @@ def test_common(self, funcname: str, state: ModuleState, args: T.Tuple[str, Buil *TEST_KWS, DEPENDENCIES_KW, LINK_WITH_KW.evolve(since='1.2.0'), + LINK_WHOLE_KW.evolve(since='1.8.0'), *RUST_TEST_KWS, ) def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> ModuleReturnValue: @@ -221,6 +226,7 @@ def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: Func *TEST_KWS_NO_ARGS, DEPENDENCIES_KW, LINK_WITH_KW, + LINK_WHOLE_KW, *RUST_TEST_KWS, KwargInfo( 'args',