compilers/rust: Add support for clippy

Clippy is a compiler wrapper for rust that provides an extra layer of
linting. It's quite popular, but unfortunately doesn't provide the
output of the compiler that it's wrapping in it's output, so we don't
detect that clippy is rustc. This small patch adds a new compiler class
(that is the Rustc class with a different id) and the necessary logic to
detect that clippy is in fact rustc)

Fixes: #8767
pull/8773/head
Dylan Baker 4 years ago
parent f0a7b6e7c6
commit 30202a2402
  1. 6
      docs/markdown/snippets/rust-clippy-driver-support.md
  2. 3
      mesonbuild/compilers/__init__.py
  3. 23
      mesonbuild/compilers/detect.py
  4. 17
      mesonbuild/compilers/rust.py
  5. 1
      test cases/rust/1 basic/clippy.toml
  6. 3
      test cases/rust/1 basic/prog.rs
  7. 13
      unittests/allplatformstests.py
  8. 5
      unittests/baseplatformtests.py

@ -0,0 +1,6 @@
## Support for clippy-driver as a rustc wrapper
Clippy is a popular linting tool for Rust, and is invoked in place of rustc as a
wrapper. Unfortunately it doesn't proxy rustc's output, so we need to have a
small wrapper around it so that Meson can correctly detect the underlying rustc,
but still display clippy

@ -112,6 +112,7 @@ __all__ = [
'PGICPPCompiler',
'PGIFortranCompiler',
'RustCompiler',
'ClippyRustCompiler',
'CcrxCCompiler',
'CcrxCPPCompiler',
'Xc16CCompiler',
@ -241,7 +242,7 @@ from .objcpp import (
ClangObjCPPCompiler,
GnuObjCPPCompiler,
)
from .rust import RustCompiler
from .rust import RustCompiler, ClippyRustCompiler
from .swift import SwiftCompiler
from .vala import ValaCompiler
from .mixins.visualstudio import VisualStudioLikeCompiler

@ -124,7 +124,7 @@ from .objcpp import (
GnuObjCPPCompiler,
)
from .cython import CythonCompiler
from .rust import RustCompiler
from .rust import RustCompiler, ClippyRustCompiler
from .swift import SwiftCompiler
from .vala import ValaCompiler
from .mixins.visualstudio import VisualStudioLikeCompiler
@ -952,6 +952,13 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
continue
version = search_version(out)
cls: T.Type[RustCompiler] = RustCompiler
# Clippy is a wrapper around rustc, but it doesn't have rustc in it's
# output. We can otherwise treat it as rustc.
if 'clippy' in out:
out = 'rustc'
cls = ClippyRustCompiler
if 'rustc' in out:
# On Linux and mac rustc will invoke gcc (clang for mac
@ -976,7 +983,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(RustCompiler.use_linker_args(cc.linker.exelist[0]))
compiler.extend(cls.use_linker_args(cc.linker.exelist[0]))
extra_args['direct'] = True
extra_args['machine'] = cc.linker.machine
else:
@ -984,7 +991,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
if 'ccache' in exelist[0]:
del exelist[0]
c = exelist.pop(0)
compiler.extend(RustCompiler.use_linker_args(c))
compiler.extend(cls.use_linker_args(c))
# Also ensure that we pass any extra arguments to the linker
for l in exelist:
@ -1002,12 +1009,12 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
**extra_args) # type: ignore
elif 'link' in override[0]:
linker = guess_win_linker(env,
override, RustCompiler, for_machine, use_linker_prefix=False)
override, cls, for_machine, use_linker_prefix=False)
# rustc takes linker arguments without a prefix, and
# inserts the correct prefix itself.
assert isinstance(linker, VisualStudioLikeLinkerMixin)
linker.direct = True
compiler.extend(RustCompiler.use_linker_args(linker.exelist[0]))
compiler.extend(cls.use_linker_args(linker.exelist[0]))
else:
# On linux and macos rust will invoke the c compiler for
# linking, on windows it will use lld-link or link.exe.
@ -1019,10 +1026,10 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
# Of course, we're not going to use any of that, we just
# need it to get the proper arguments to pass to rustc
c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0]
compiler.extend(RustCompiler.use_linker_args(c))
compiler.extend(cls.use_linker_args(c))
env.coredata.add_lang_args(RustCompiler.language, RustCompiler, for_machine, env)
return RustCompiler(
env.coredata.add_lang_args(cls.language, cls, for_machine, env)
return cls(
compiler, version, for_machine, is_cross, info, exe_wrap,
linker=linker)

@ -196,3 +196,20 @@ class RustCompiler(Compiler):
# Rustc currently has no way to toggle this, it's controlled by whether
# pic is on by rustc
return []
class ClippyRustCompiler(RustCompiler):
"""Clippy is a linter that wraps Rustc.
This just provides us a different id
"""
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
is_cross: bool, info: 'MachineInfo',
exe_wrapper: T.Optional['ExternalProgram'] = None,
full_version: T.Optional[str] = None,
linker: T.Optional['DynamicLinker'] = None):
super().__init__(exelist, version, for_machine, is_cross, info,
exe_wrapper, full_version, linker)
self.id = 'clippy-driver rustc'

@ -0,0 +1 @@
blacklisted-names = ["foo"]

@ -1,3 +1,4 @@
fn main() {
println!("rust compiler is working");
let foo = "rust compiler is working";
println!("{}", foo);
}

@ -4034,3 +4034,16 @@ class AllPlatformTests(BasePlatformTests):
for file, details in files.items():
with self.subTest(key='{}.{}'.format(data_type, file)):
self.assertEqual(res[data_type][file], details)
@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')
# Wehn 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, extra_args=['--werror'], override_envvars={'RUSTC': 'clippy-driver'})
with self.assertRaises(subprocess.CalledProcessError) as cm:
self.build()
self.assertIn('error: use of a blacklisted/placeholder name `foo`', cm.exception.stdout)

@ -69,6 +69,7 @@ class BasePlatformTests(TestCase):
self.uninstall_command = get_backend_commands(self.backend)
# Test directories
self.common_test_dir = os.path.join(src_root, 'test cases/common')
self.rust_test_dir = os.path.join(src_root, 'test cases/rust')
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
@ -135,7 +136,7 @@ class BasePlatformTests(TestCase):
os.environ.update(self.orig_env)
super().tearDown()
def _run(self, command, *, workdir=None, override_envvars=None):
def _run(self, command, *, workdir=None, override_envvars: T.Optional[T.Mapping[str, str]] = None):
'''
Run a command while printing the stdout and stderr to stdout,
and also return a copy of it
@ -164,7 +165,7 @@ class BasePlatformTests(TestCase):
extra_args=None,
default_args=True,
inprocess=False,
override_envvars=None,
override_envvars: T.Optional[T.Mapping[str, str]] = None,
workdir=None,
allow_fail: bool = False) -> str:
"""Call `meson setup`

Loading…
Cancel
Save