scripts: ci: Add CI bindings style checker
Implement a check in the CI pipeline to enforce that property names in device tree bindings do not contain underscores. Signed-off-by: James Roy <rruuaanng@outlook.com> Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
parent
9cf9416dec
commit
4240853a06
2 changed files with 97 additions and 29 deletions
11
scripts/bindings_properties_allowlist.yaml
Normal file
11
scripts/bindings_properties_allowlist.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# This file is a YAML allowlist of property names that are allowed to
|
||||||
|
# bypass the underscore check in bindings. These properties are exempt
|
||||||
|
# from the rule that requires using '-' instead of '_'.
|
||||||
|
#
|
||||||
|
# The file content can be as shown below:
|
||||||
|
# - propname1
|
||||||
|
# - propname2
|
||||||
|
# - ...
|
||||||
|
|
||||||
|
- mmc-hs200-1_8v
|
||||||
|
- mmc-hs400-1_8v
|
|
@ -21,6 +21,7 @@ import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
import unidiff
|
import unidiff
|
||||||
|
import yaml
|
||||||
|
|
||||||
from yamllint import config, linter
|
from yamllint import config, linter
|
||||||
|
|
||||||
|
@ -35,6 +36,30 @@ from get_maintainer import Maintainers, MaintainersError
|
||||||
import list_boards
|
import list_boards
|
||||||
import list_hardware
|
import list_hardware
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2]
|
||||||
|
/ "scripts" / "dts" / "python-devicetree" / "src"))
|
||||||
|
from devicetree import edtlib
|
||||||
|
|
||||||
|
|
||||||
|
# Let the user run this script as ./scripts/ci/check_compliance.py without
|
||||||
|
# making them set ZEPHYR_BASE.
|
||||||
|
ZEPHYR_BASE = os.environ.get('ZEPHYR_BASE')
|
||||||
|
if ZEPHYR_BASE:
|
||||||
|
ZEPHYR_BASE = Path(ZEPHYR_BASE)
|
||||||
|
else:
|
||||||
|
ZEPHYR_BASE = Path(__file__).resolve().parents[2]
|
||||||
|
# Propagate this decision to child processes.
|
||||||
|
os.environ['ZEPHYR_BASE'] = str(ZEPHYR_BASE)
|
||||||
|
|
||||||
|
# Initialize the property names allowlist
|
||||||
|
BINDINGS_PROPERTIES_AL = None
|
||||||
|
with open(Path(__file__).parents[1] / 'bindings_properties_allowlist.yaml') as f:
|
||||||
|
allowlist = yaml.safe_load(f.read())
|
||||||
|
if allowlist is not None:
|
||||||
|
BINDINGS_PROPERTIES_AL = set(allowlist)
|
||||||
|
else:
|
||||||
|
BINDINGS_PROPERTIES_AL = set()
|
||||||
|
|
||||||
logger = None
|
logger = None
|
||||||
|
|
||||||
def git(*args, cwd=None, ignore_non_zero=False):
|
def git(*args, cwd=None, ignore_non_zero=False):
|
||||||
|
@ -380,32 +405,75 @@ class DevicetreeBindingsCheck(ComplianceTest):
|
||||||
doc = "See https://docs.zephyrproject.org/latest/build/dts/bindings.html for more details."
|
doc = "See https://docs.zephyrproject.org/latest/build/dts/bindings.html for more details."
|
||||||
|
|
||||||
def run(self, full=True):
|
def run(self, full=True):
|
||||||
dts_bindings = self.parse_dt_bindings()
|
bindings_diff, bindings = self.get_yaml_bindings()
|
||||||
|
|
||||||
for dts_binding in dts_bindings:
|
# If no bindings are changed, skip this check.
|
||||||
self.required_false_check(dts_binding)
|
try:
|
||||||
|
subprocess.check_call(['git', 'diff', '--quiet', COMMIT_RANGE]
|
||||||
|
+ bindings_diff)
|
||||||
|
nodiff = True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
nodiff = False
|
||||||
|
if nodiff:
|
||||||
|
self.skip('no changes to bindings were made')
|
||||||
|
|
||||||
def parse_dt_bindings(self):
|
for binding in bindings:
|
||||||
|
self.check(binding, self.check_yaml_property_name)
|
||||||
|
self.check(binding, self.required_false_check)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def check(binding, callback):
|
||||||
|
while binding is not None:
|
||||||
|
callback(binding)
|
||||||
|
binding = binding.child_binding
|
||||||
|
|
||||||
|
def get_yaml_bindings(self):
|
||||||
"""
|
"""
|
||||||
Returns a list of dts/bindings/**/*.yaml files
|
Returns a list of 'dts/bindings/**/*.yaml'
|
||||||
"""
|
"""
|
||||||
|
from glob import glob
|
||||||
|
BINDINGS_PATH = 'dts/bindings/'
|
||||||
|
bindings_diff_dir, bindings = set(), []
|
||||||
|
|
||||||
dt_bindings = []
|
for file_name in get_files(filter='d'):
|
||||||
for file_name in get_files(filter="d"):
|
if BINDINGS_PATH in file_name:
|
||||||
if 'dts/bindings/' in file_name and file_name.endswith('.yaml'):
|
p = file_name.partition(BINDINGS_PATH)
|
||||||
dt_bindings.append(file_name)
|
bindings_diff_dir.add(os.path.join(p[0], p[1]))
|
||||||
|
|
||||||
return dt_bindings
|
for path in bindings_diff_dir:
|
||||||
|
yamls = glob(f'{os.fspath(path)}/**/*.yaml', recursive=True)
|
||||||
|
bindings.extend(yamls)
|
||||||
|
|
||||||
def required_false_check(self, dts_binding):
|
bindings = edtlib.bindings_from_paths(bindings, ignore_errors=True)
|
||||||
with open(dts_binding) as file:
|
return list(bindings_diff_dir), bindings
|
||||||
for line_number, line in enumerate(file, 1):
|
|
||||||
if 'required: false' in line:
|
|
||||||
self.fmtd_failure(
|
|
||||||
'warning', 'Devicetree Bindings', dts_binding,
|
|
||||||
line_number, col=None,
|
|
||||||
desc="'required: false' is redundant, please remove")
|
|
||||||
|
|
||||||
|
def check_yaml_property_name(self, binding):
|
||||||
|
"""
|
||||||
|
Checks if the property names in the binding file contain underscores.
|
||||||
|
"""
|
||||||
|
for prop_name in binding.prop2specs:
|
||||||
|
if '_' in prop_name and prop_name not in BINDINGS_PROPERTIES_AL:
|
||||||
|
better_prop = prop_name.replace('_', '-')
|
||||||
|
print(f"Required: In '{binding.path}', "
|
||||||
|
f"the property '{prop_name}' "
|
||||||
|
f"should be renamed to '{better_prop}'.")
|
||||||
|
self.failure(
|
||||||
|
f"{binding.path}: property '{prop_name}' contains underscores.\n"
|
||||||
|
f"\tUse '{better_prop}' instead unless this property name is from Linux.\n"
|
||||||
|
"Or another authoritative upstream source of bindings for "
|
||||||
|
f"compatible '{binding.compatible}'.\n"
|
||||||
|
"\tHint: update 'bindings_properties_allowlist.yaml' if you need to "
|
||||||
|
"override this check for this property."
|
||||||
|
)
|
||||||
|
|
||||||
|
def required_false_check(self, binding):
|
||||||
|
raw_props = binding.raw.get('properties', {})
|
||||||
|
for prop_name, raw_prop in raw_props.items():
|
||||||
|
if raw_prop.get('required') is False:
|
||||||
|
self.failure(
|
||||||
|
f'{binding.path}: property "{prop_name}": '
|
||||||
|
"'required: false' is redundant, please remove"
|
||||||
|
)
|
||||||
|
|
||||||
class KconfigCheck(ComplianceTest):
|
class KconfigCheck(ComplianceTest):
|
||||||
"""
|
"""
|
||||||
|
@ -2001,17 +2069,6 @@ def _main(args):
|
||||||
# The "real" main(), which is wrapped to catch exceptions and report them
|
# The "real" main(), which is wrapped to catch exceptions and report them
|
||||||
# to GitHub. Returns the number of test failures.
|
# to GitHub. Returns the number of test failures.
|
||||||
|
|
||||||
global ZEPHYR_BASE
|
|
||||||
ZEPHYR_BASE = os.environ.get('ZEPHYR_BASE')
|
|
||||||
if not ZEPHYR_BASE:
|
|
||||||
# Let the user run this script as ./scripts/ci/check_compliance.py without
|
|
||||||
# making them set ZEPHYR_BASE.
|
|
||||||
ZEPHYR_BASE = str(Path(__file__).resolve().parents[2])
|
|
||||||
|
|
||||||
# Propagate this decision to child processes.
|
|
||||||
os.environ['ZEPHYR_BASE'] = ZEPHYR_BASE
|
|
||||||
ZEPHYR_BASE = Path(ZEPHYR_BASE)
|
|
||||||
|
|
||||||
# The absolute path of the top-level git directory. Initialize it here so
|
# The absolute path of the top-level git directory. Initialize it here so
|
||||||
# that issues running Git can be reported to GitHub.
|
# that issues running Git can be reported to GitHub.
|
||||||
global GIT_TOP
|
global GIT_TOP
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue