blob: f197f2069bcab35ff7613c0e20e4db440b93240c [file] [edit]
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0
"""Reject Bazel's `features` attribute on Rust targets.
`features` is a Bazel-wide attribute (toolchain features), not a Cargo feature.
On a rules_rust target it is silently accepted and never sets a Rust feature,
so `#[cfg(feature = "...")]` code is compiled out and a test can pass without
running anything. Cargo features are set with `crate_features`.
"""
import ast
import logging
from pw_presubmit.presubmit import PresubmitContext, PresubmitFailure, filter_paths
_LOG = logging.getLogger("rust_features")
_RUST_RULES = frozenset(
(
# Pigweed wrapper that forwards **kwargs to rust_binary.
"rust_app",
"rust_binary",
"rust_library",
"rust_proc_macro",
"rust_shared_library",
"rust_static_library",
"rust_test",
)
)
def find_violations(source: str) -> list[tuple[int, str]]:
"""Returns (line, rule) for each Rust rule call that sets `features`.
BUILD files are parsed as Python, which is a superset of the Starlark
subset they use. Rules are matched by call name, so a renamed load
(`load(..., rust_lib = "rust_library")`) is not caught. Raises SyntaxError
if the source cannot be parsed.
"""
violations = []
for node in ast.walk(ast.parse(source)):
if not isinstance(node, ast.Call):
continue
func = node.func
name = func.id if isinstance(func, ast.Name) else None
if name not in _RUST_RULES:
continue
if any(kw.arg == "features" for kw in node.keywords):
violations.append((node.lineno, name))
return sorted(violations)
@filter_paths(endswith=(".bazel", ".bzl"), exclude=(r"\bthird_party/",))
def rust_features_check(ctx: PresubmitContext):
"""Checks that Rust targets use `crate_features`, not `features`."""
failed = False
for path in ctx.paths:
try:
violations = find_violations(path.read_text())
except (SyntaxError, UnicodeDecodeError) as exc:
raise PresubmitFailure(f"failed to parse {path}: {exc}") from exc
for line, rule in violations:
failed = True
_LOG.warning(
"%s:%d: `features` on %s is Bazel's toolchain attribute and "
"does not enable Rust features; use `crate_features`",
path,
line,
rule,
)
if failed:
raise PresubmitFailure