summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2024-11-18 11:20:56 +0100
committerDylan Baker <dylan@pnwbakers.com>2024-12-19 09:25:20 -0800
commit27c567de5d1807ac72708ea48018a21f0c6b8dd2 (patch)
tree180bc008df788970020b66ae677ea0f4e4a2d0e5
parent5dc537afd051e60ff00731f73e02d98138d9198b (diff)
downloadmeson-27c567de5d1807ac72708ea48018a21f0c6b8dd2.tar.gz
scripts: add "clippy" internal tool
Similar to the "ninja scan-build" target for C, add a clippy internal tool that runs clippy-driver on all crates in the project. The approach used is more efficient than with "ninja scan-build", and does not require rerunning Meson in a separate build directory; it uses the introspection data to find the compiler arguments for the target and invokes clippy-driver with a slightly modified command line. This could actually be applied to scan-build as well, reusing the run_tool_on_targets() function. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rw-r--r--mesonbuild/compilers/rust.py49
-rw-r--r--mesonbuild/scripts/clippy.py67
-rw-r--r--mesonbuild/scripts/run_tool.py10
3 files changed, 126 insertions, 0 deletions
diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py
index 02ac59384..717d5635f 100644
--- a/mesonbuild/compilers/rust.py
+++ b/mesonbuild/compilers/rust.py
@@ -33,6 +33,35 @@ rust_optimization_args: T.Dict[str, T.List[str]] = {
's': ['-C', 'opt-level=s'],
}
+def get_rustup_run_and_args(exelist: T.List[str]) -> T.Optional[T.Tuple[T.List[str], T.List[str]]]:
+ """Given the command for a rustc executable, check if it is invoked via
+ "rustup run" and if so separate the "rustup [OPTIONS] run TOOLCHAIN"
+ part from the arguments to rustc. If the returned value is not None,
+ other tools (for example clippy-driver or rustdoc) can be run by placing
+ the name of the tool between the two elements of the tuple."""
+ e = iter(exelist)
+ try:
+ if os.path.basename(next(e)) != 'rustup':
+ return None
+ # minimum three strings: "rustup run TOOLCHAIN"
+ n = 3
+ opt = next(e)
+
+ # options come first
+ while opt.startswith('-'):
+ n += 1
+ opt = next(e)
+
+ # then "run TOOLCHAIN"
+ if opt != 'run':
+ return None
+
+ next(e)
+ next(e)
+ return exelist[:n], list(e)
+ except StopIteration:
+ return None
+
class RustCompiler(Compiler):
# rustc doesn't invoke the compiler itself, it doesn't need a LINKER_PREFIX
@@ -65,6 +94,7 @@ class RustCompiler(Compiler):
super().__init__([], exelist, version, for_machine, info,
is_cross=is_cross, full_version=full_version,
linker=linker)
+ self.rustup_run_and_args: T.Optional[T.Tuple[T.List[str], T.List[str]]] = get_rustup_run_and_args(exelist)
self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_ndebug']})
if 'link' in self.linker.id:
self.base_options.add(OptionKey('b_vscrt'))
@@ -252,6 +282,25 @@ class RustCompiler(Compiler):
action = "no" if disable else "yes"
return ['-C', f'debug-assertions={action}', '-C', 'overflow-checks=no']
+ def get_rust_tool(self, name: str, env: Environment) -> T.List[str]:
+ if self.rustup_run_and_args:
+ rustup_exelist, args = self.rustup_run_and_args
+ # do not use extend so that exelist is copied
+ exelist = rustup_exelist + [name]
+ else:
+ exelist = [name]
+ args = self.exelist[1:]
+
+ from ..programs import find_external_program
+ for prog in find_external_program(env, self.for_machine, exelist[0], exelist[0],
+ [exelist[0]], allow_default_for_cross=False):
+ exelist[0] = prog.path
+ break
+ else:
+ return []
+
+ return exelist + args
+
class ClippyRustCompiler(RustCompiler):
diff --git a/mesonbuild/scripts/clippy.py b/mesonbuild/scripts/clippy.py
new file mode 100644
index 000000000..a5161462c
--- /dev/null
+++ b/mesonbuild/scripts/clippy.py
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2024 The Meson development team
+
+from __future__ import annotations
+from collections import defaultdict
+import os
+import tempfile
+import typing as T
+
+from .run_tool import run_tool_on_targets, run_with_buffered_output
+from .. import build, mlog
+from ..mesonlib import MachineChoice, PerMachine
+
+if T.TYPE_CHECKING:
+ from ..compilers.rust import RustCompiler
+
+class ClippyDriver:
+ def __init__(self, build: build.Build, tempdir: str):
+ self.tools: PerMachine[T.List[str]] = PerMachine([], [])
+ self.warned: T.DefaultDict[str, bool] = defaultdict(lambda: False)
+ self.tempdir = tempdir
+ for machine in MachineChoice:
+ compilers = build.environment.coredata.compilers[machine]
+ if 'rust' in compilers:
+ compiler = T.cast('RustCompiler', compilers['rust'])
+ self.tools[machine] = compiler.get_rust_tool('clippy-driver', build.environment)
+
+ def warn_missing_clippy(self, machine: str) -> None:
+ if self.warned[machine]:
+ return
+ mlog.warning(f'clippy-driver not found for {machine} machine')
+ self.warned[machine] = True
+
+ def __call__(self, target: T.Dict[str, T.Any]) -> T.Iterable[T.Coroutine[None, None, int]]:
+ for src_block in target['target_sources']:
+ if src_block['language'] == 'rust':
+ clippy = getattr(self.tools, src_block['machine'])
+ if not clippy:
+ self.warn_missing_clippy(src_block['machine'])
+ continue
+
+ cmdlist = list(clippy)
+ prev = None
+ for arg in src_block['parameters']:
+ if prev:
+ prev = None
+ continue
+ elif arg in {'--emit', '--out-dir'}:
+ prev = arg
+ else:
+ cmdlist.append(arg)
+
+ cmdlist.extend(src_block['sources'])
+ # the default for --emit is to go all the way to linking,
+ # and --emit dep-info= is not enough for clippy to do
+ # enough analysis, so use --emit metadata.
+ cmdlist.append('--emit')
+ cmdlist.append('metadata')
+ cmdlist.append('--out-dir')
+ cmdlist.append(self.tempdir)
+ yield run_with_buffered_output(cmdlist)
+
+def run(args: T.List[str]) -> int:
+ os.chdir(args[0])
+ build_data = build.load(os.getcwd())
+ with tempfile.TemporaryDirectory() as d:
+ return run_tool_on_targets(ClippyDriver(build_data, d))
diff --git a/mesonbuild/scripts/run_tool.py b/mesonbuild/scripts/run_tool.py
index bccc4cb83..e206ff7fe 100644
--- a/mesonbuild/scripts/run_tool.py
+++ b/mesonbuild/scripts/run_tool.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import asyncio.subprocess
import fnmatch
import itertools
+import json
import signal
import sys
from pathlib import Path
@@ -126,3 +127,12 @@ def run_clang_tool(name: str, srcdir: Path, builddir: Path, fn: T.Callable[...,
def wrapper(path: Path) -> T.Iterable[T.Coroutine[None, None, int]]:
yield fn(path, *args)
return asyncio.run(_run_workers(all_clike_files(name, srcdir, builddir), wrapper))
+
+def run_tool_on_targets(fn: T.Callable[[T.Dict[str, T.Any]],
+ T.Iterable[T.Coroutine[None, None, int]]]) -> int:
+ if sys.platform == 'win32':
+ asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
+
+ with open('meson-info/intro-targets.json', encoding='utf-8') as fp:
+ targets = json.load(fp)
+ return asyncio.run(_run_workers(targets, fn))