From 5768ccba6eb77167da96712c20a4e042efb31d03 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sat, 16 Nov 2024 16:05:05 +0100 Subject: [PATCH] ninjabackend: add support for "ninja clippy" Add a target that builds all crates that could be extern to others, and then reruns clippy. Signed-off-by: Paolo Bonzini --- docs/markdown/howtox.md | 20 ++++++++++++++++++++ docs/markdown/snippets/clippy.md | 5 +++++ docs/markdown/snippets/num-processes.md | 2 +- mesonbuild/backend/backends.py | 6 ++++++ mesonbuild/backend/ninjabackend.py | 22 +++++++++++++++++++++- unittests/allplatformstests.py | 18 ++++++++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 docs/markdown/snippets/clippy.md diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md index 4a57e8569137..ba6a3b8f8d63 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -239,6 +239,26 @@ And then pass it through the variable (remember to use absolute path): $ SCANBUILD=$(pwd)/my-scan-build.sh ninja -C builddir scan-build ``` +## Use clippy + +If your project includes Rust targets, you can invoke clippy like this: + +```console +$ meson setup builddir +$ ninja -C builddir clippy +``` + +Clippy will also obey the `werror` [builtin option](Builtin-options.md#core-options). + +By default Meson uses as many concurrent processes as there are cores +on the test machine. You can override this with the environment +variable `MESON_NUM_PROCESSES`. + +Meson will look for `clippy-driver` in the same directory as `rustc`, +or try to invoke it using `rustup` if `rustc` points to a `rustup` +binary. If `clippy-driver` is not detected properly, you can add it to +a [machine file](Machine-files.md). + ## Use profile guided optimization Using profile guided optimization with GCC is a two phase diff --git a/docs/markdown/snippets/clippy.md b/docs/markdown/snippets/clippy.md new file mode 100644 index 000000000000..14bcc77428a1 --- /dev/null +++ b/docs/markdown/snippets/clippy.md @@ -0,0 +1,5 @@ +## Meson can run "clippy" on Rust projects + +Meson now defines a `clippy` target if the project uses the Rust programming +language. The target runs clippy on all Rust sources, using the `clippy-driver` +program from the same Rust toolchain as the `rustc` compiler. diff --git a/docs/markdown/snippets/num-processes.md b/docs/markdown/snippets/num-processes.md index c484d930f287..5336377900ce 100644 --- a/docs/markdown/snippets/num-processes.md +++ b/docs/markdown/snippets/num-processes.md @@ -5,4 +5,4 @@ the amount of parallel jobs to run; this was useful when `meson test` is invoked through `ninja test` for example. With this version, a new variable `MESON_NUM_PROCESSES` is supported with a broader scope: in addition to `meson test`, it is also used by the `external_project` module and by -Ninja targets that invoke `clang-tidy` and `clang-format`. +Ninja targets that invoke `clang-tidy`, `clang-format` and `clippy`. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index d2c6a46965f6..970fb82f2b4e 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -2040,6 +2040,12 @@ def compiler_to_generator_args(self, target: build.BuildTarget, commands += [input] return commands + def have_language(self, langname: str) -> bool: + for for_machine in MachineChoice: + if langname in self.environment.coredata.compilers[for_machine]: + return True + return False + def compiler_to_generator(self, target: build.BuildTarget, compiler: 'Compiler', sources: _ALL_SOURCES_TYPE, diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 81b8bb51ab81..0c34e08deb39 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -463,6 +463,8 @@ class RustCrate: display_name: str root_module: str + crate_type: str + target_name: str edition: RUST_EDITIONS deps: T.List[RustDep] cfg: T.List[str] @@ -1878,6 +1880,7 @@ def __generate_sources_structure(self, root: Path, structured_sources: build.Str return orderdeps, first_file def _add_rust_project_entry(self, name: str, main_rust_file: str, args: CompilerArgs, + crate_type: str, target_name: str, from_subproject: bool, proc_macro_dylib_path: T.Optional[str], deps: T.List[RustDep]) -> None: raw_edition: T.Optional[str] = mesonlib.first(reversed(args), lambda x: x.startswith('--edition')) @@ -1895,6 +1898,8 @@ def _add_rust_project_entry(self, name: str, main_rust_file: str, args: Compiler len(self.rust_crates), name, main_rust_file, + crate_type, + target_name, edition, deps, cfg, @@ -2134,7 +2139,7 @@ def _link_library(libname: str, static: bool, bundle: bool = False): self._add_rust_project_entry(target.name, os.path.abspath(os.path.join(self.environment.build_dir, main_rust_file)), - args, + args, cratetype, target_name, bool(target.subproject), proc_macro_dylib_path, project_deps) @@ -3640,6 +3645,20 @@ def generate_dist(self) -> None: elem.add_item('pool', 'console') self.add_build(elem) + def generate_clippy(self) -> None: + if 'clippy' in self.all_outputs or not self.have_language('rust'): + return + + cmd = self.environment.get_build_command() + \ + ['--internal', 'clippy', self.environment.build_dir] + elem = self.create_phony_target('clippy', 'CUSTOM_COMMAND', 'PHONY') + elem.add_item('COMMAND', cmd) + elem.add_item('pool', 'console') + for crate in self.rust_crates.values(): + if crate.crate_type in {'rlib', 'dylib', 'proc-macro'}: + elem.add_dep(crate.target_name) + self.add_build(elem) + def generate_scanbuild(self) -> None: if not environment.detect_scanbuild(): return @@ -3707,6 +3726,7 @@ def generate_utils(self) -> None: self.generate_scanbuild() self.generate_clangformat() self.generate_clangtidy() + self.generate_clippy() self.generate_tags('etags', 'TAGS') self.generate_tags('ctags', 'ctags') self.generate_tags('cscope', 'cscope') diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 1b1e16928276..6544bcce19ff 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -4887,6 +4887,24 @@ def output_name(name, type_): @skip_if_not_language('rust') @unittest.skipIf(not shutil.which('clippy-driver'), 'Test requires clippy-driver') def test_rust_clippy(self) -> None: + if self.backend is not Backend.ninja: + raise unittest.SkipTest('Rust is only supported with ninja currently') + # When clippy is used, we should get an exception since a variable named + # "foo" is used, but is on our denylist + testdir = os.path.join(self.rust_test_dir, '1 basic') + self.init(testdir) + self.build('clippy') + + self.wipe() + self.init(testdir, extra_args=['--werror', '-Db_colorout=never']) + with self.assertRaises(subprocess.CalledProcessError) as cm: + self.build('clippy') + self.assertTrue('error: use of a blacklisted/placeholder name `foo`' in cm.exception.stdout or + 'error: use of a disallowed/placeholder name `foo`' in cm.exception.stdout) + + @skip_if_not_language('rust') + @unittest.skipIf(not shutil.which('clippy-driver'), 'Test requires clippy-driver') + def test_rust_clippy_as_rustc(self) -> None: if self.backend is not Backend.ninja: raise unittest.SkipTest('Rust is only supported with ninja currently') # When clippy is used, we should get an exception since a variable named