summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2025-10-23 15:33:20 +0200
committerXavier Claessens <xclaesse@gmail.com>2025-10-23 17:02:13 +0100
commit0fcc18171b88ca5f9fa471fd204d6ee0129bd58c (patch)
tree20efa974cffcad19776fb923ff3d73899b611c40
parentab26e300470a42950f95bcdc9cd38c670d7a74c7 (diff)
downloadmeson-0fcc18171b88ca5f9fa471fd204d6ee0129bd58c.tar.gz
cargo: parse lints table
The lints table in Cargo.toml has a very human-targeted syntax. When building manifest.from_raw, flatten everything into a single list, prefixing the tool name to every warning option and sorting by priority. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
-rw-r--r--mesonbuild/cargo/manifest.py50
-rw-r--r--mesonbuild/cargo/raw.py22
-rw-r--r--unittests/cargotests.py54
3 files changed, 123 insertions, 3 deletions
diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py
index 5440437c1..ec10d4611 100644
--- a/mesonbuild/cargo/manifest.py
+++ b/mesonbuild/cargo/manifest.py
@@ -19,7 +19,7 @@ if T.TYPE_CHECKING:
from typing_extensions import Protocol, Self
from . import raw
- from .raw import EDITION, CRATE_TYPE
+ from .raw import EDITION, CRATE_TYPE, LINT_LEVEL
from ..wrap.wrap import PackageDefinition
# Copied from typeshed. Blarg that they don't expose this
@@ -420,6 +420,41 @@ class Example(BuildTarget):
@dataclasses.dataclass
+class Lint:
+
+ """Cargo Lint definition.
+ """
+
+ name: str
+ level: LINT_LEVEL
+ priority: int
+ check_cfg: T.Optional[T.List[str]]
+
+ @classmethod
+ def from_raw(cls, r: T.Union[raw.FromWorkspace, T.Dict[str, T.Dict[str, raw.LintV]]]) -> T.List[Lint]:
+ r = T.cast('T.Dict[str, T.Dict[str, raw.LintV]]', r)
+ lints: T.Dict[str, Lint] = {}
+ for tool, raw_lints in r.items():
+ prefix = '' if tool == 'rust' else f'{tool}::'
+ for name, settings in raw_lints.items():
+ name = prefix + name
+ if isinstance(settings, str):
+ settings = T.cast('raw.Lint', {'level': settings})
+ check_cfg = None
+ if name == 'unexpected_cfgs':
+ # 'cfg(test)' is added automatically by cargo
+ check_cfg = ['cfg(test)'] + settings.get('check-cfg', [])
+ lints[name] = Lint(name=name,
+ level=settings['level'],
+ priority=settings.get('priority', 0),
+ check_cfg=check_cfg)
+
+ lints_final = list(lints.values())
+ lints_final.sort(key=lambda x: x.priority)
+ return lints_final
+
+
+@dataclasses.dataclass
class Manifest:
"""Cargo Manifest definition.
@@ -444,6 +479,7 @@ class Manifest:
example: T.List[Example] = dataclasses.field(default_factory=list)
features: T.Dict[str, T.List[str]] = dataclasses.field(default_factory=dict)
target: T.Dict[str, T.Dict[str, Dependency]] = dataclasses.field(default_factory=dict)
+ lints: T.List[Lint] = dataclasses.field(default_factory=list)
# missing: profile
@@ -466,11 +502,13 @@ class Manifest:
return {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()}
return _raw_to_dataclass(raw, cls, f'Cargo.toml package {pkg.name}',
+ raw_from_workspace=workspace.inheritable if workspace else None,
ignored_fields=['badges', 'workspace'],
package=ConvertValue(lambda _: pkg),
dependencies=ConvertValue(dependencies_from_raw),
dev_dependencies=ConvertValue(dependencies_from_raw),
build_dependencies=ConvertValue(dependencies_from_raw),
+ lints=ConvertValue(Lint.from_raw),
lib=ConvertValue(lambda x: Library.from_raw(x, pkg), default=autolib),
bin=ConvertValue(lambda x: [Binary.from_raw(b, pkg) for b in x]),
test=ConvertValue(lambda x: [Test.from_raw(b, pkg) for b in x]),
@@ -493,12 +531,20 @@ class Workspace:
# inheritable settings are kept in raw format, for use with _raw_to_dataclass
package: T.Optional[raw.Package] = None
dependencies: T.Dict[str, raw.Dependency] = dataclasses.field(default_factory=dict)
- lints: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict)
+ lints: T.Dict[str, T.Dict[str, raw.LintV]] = dataclasses.field(default_factory=dict)
metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict)
# A workspace can also have a root package.
root_package: T.Optional[Manifest] = None
+ @lazy_property
+ def inheritable(self) -> T.Dict[str, object]:
+ # the whole lints table is inherited. Do not add package, dependencies
+ # etc. because they can only be inherited a field at a time.
+ return {
+ 'lints': self.lints,
+ }
+
@classmethod
def from_raw(cls, raw: raw.Manifest, path: str) -> Self:
ws = _raw_to_dataclass(raw['workspace'], cls, 'Workspace')
diff --git a/mesonbuild/cargo/raw.py b/mesonbuild/cargo/raw.py
index 3b710d022..b683f06d8 100644
--- a/mesonbuild/cargo/raw.py
+++ b/mesonbuild/cargo/raw.py
@@ -10,6 +10,7 @@ from typing_extensions import Literal, TypedDict, Required
EDITION = Literal['2015', '2018', '2021']
CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro']
+LINT_LEVEL = Literal['allow', 'deny', 'forbid', 'warn']
class FromWorkspace(TypedDict):
@@ -121,6 +122,26 @@ class Target(TypedDict):
dependencies: T.Dict[str, T.Union[FromWorkspace, DependencyV]]
+Lint = TypedDict(
+ 'Lint',
+ {
+ 'level': Required[LINT_LEVEL],
+ 'priority': int,
+ 'check-cfg': T.List[str],
+ },
+ total=True,
+)
+"""The representation of a linter setting.
+
+This does not include the name or tool, since those are the keys of the
+dictionaries that point to Lint.
+"""
+
+
+LintV = T.Union[Lint, str]
+"""A Lint entry, either a string or a Lint Dict."""
+
+
class Workspace(TypedDict):
"""The representation of a workspace.
@@ -154,6 +175,7 @@ Manifest = TypedDict(
'features': T.Dict[str, T.List[str]],
'target': T.Dict[str, Target],
'workspace': Workspace,
+ 'lints': T.Union[FromWorkspace, T.Dict[str, T.Dict[str, LintV]]],
# TODO: patch?
# TODO: replace?
diff --git a/unittests/cargotests.py b/unittests/cargotests.py
index 3dd809dca..0c2b645f5 100644
--- a/unittests/cargotests.py
+++ b/unittests/cargotests.py
@@ -11,7 +11,7 @@ import typing as T
from mesonbuild.cargo import cfg
from mesonbuild.cargo.cfg import TokenType
from mesonbuild.cargo.interpreter import load_cargo_lock
-from mesonbuild.cargo.manifest import Dependency, Manifest, Package, Workspace
+from mesonbuild.cargo.manifest import Dependency, Lint, Manifest, Package, Workspace
from mesonbuild.cargo.toml import load_toml
from mesonbuild.cargo.version import convert
@@ -246,6 +246,13 @@ class CargoTomlTest(unittest.TestCase):
async-channel = "2.0"
zerocopy = { version = "0.7", features = ["derive"] }
+ [lints.rust]
+ unknown_lints = "allow"
+ unexpected_cfgs = { level = "deny", check-cfg = [ 'cfg(MESON)' ] }
+
+ [lints.clippy]
+ pedantic = {level = "warn", priority = -1}
+
[dev-dependencies.gir-format-check]
version = "^0.1"
''')
@@ -308,8 +315,29 @@ class CargoTomlTest(unittest.TestCase):
gtk = { package = "gtk4", version = "0.9" }
once_cell = "1.0"
syn = { version = "2", features = ["parse"] }
+
+ [workspace.lints.rust]
+ warnings = "deny"
''')
+ def test_cargo_toml_ws_lints(self) -> None:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ fname = os.path.join(tmpdir, 'Cargo.toml')
+ with open(fname, 'w', encoding='utf-8') as f:
+ f.write(self.CARGO_TOML_WS)
+ workspace_toml = load_toml(fname)
+
+ workspace = Workspace.from_raw(workspace_toml, tmpdir)
+ pkg = Manifest.from_raw({'package': {'name': 'foo'},
+ 'lints': {'workspace': True}}, 'Cargo.toml', workspace)
+ lints = pkg.lints
+ self.assertEqual(lints[0].name, 'warnings')
+ self.assertEqual(lints[0].level, 'deny')
+ self.assertEqual(lints[0].priority, 0)
+
+ pkg = Manifest.from_raw({'package': {'name': 'bar'}}, 'Cargo.toml', workspace)
+ self.assertEqual(pkg.lints, [])
+
def test_cargo_toml_ws_package(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
fname = os.path.join(tmpdir, 'Cargo.toml')
@@ -378,6 +406,30 @@ class CargoTomlTest(unittest.TestCase):
print(manifest.package.metadata)
self.assertEqual(len(manifest.package.metadata), 1)
+ def test_cargo_toml_lints(self) -> None:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ fname = os.path.join(tmpdir, 'Cargo.toml')
+ with open(fname, 'w', encoding='utf-8') as f:
+ f.write(self.CARGO_TOML_1)
+ manifest_toml = load_toml(fname)
+ manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml')
+
+ self.assertEqual(len(manifest.lints), 3)
+ self.assertEqual(manifest.lints[0].name, 'clippy::pedantic')
+ self.assertEqual(manifest.lints[0].level, 'warn')
+ self.assertEqual(manifest.lints[0].priority, -1)
+ self.assertEqual(manifest.lints[0].check_cfg, None)
+
+ self.assertEqual(manifest.lints[1].name, 'unknown_lints')
+ self.assertEqual(manifest.lints[1].level, 'allow')
+ self.assertEqual(manifest.lints[1].priority, 0)
+ self.assertEqual(manifest.lints[1].check_cfg, None)
+
+ self.assertEqual(manifest.lints[2].name, 'unexpected_cfgs')
+ self.assertEqual(manifest.lints[2].level, 'deny')
+ self.assertEqual(manifest.lints[2].priority, 0)
+ self.assertEqual(manifest.lints[2].check_cfg, ['cfg(test)', 'cfg(MESON)'])
+
def test_cargo_toml_dependencies(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
fname = os.path.join(tmpdir, 'Cargo.toml')