Skip to content

Commit

Permalink
scripts: ci: Add CI bindings style checker
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rruuaanng committed Dec 27, 2024
1 parent 88007bd commit 0e899c5
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 29 deletions.
11 changes: 11 additions & 0 deletions scripts/ci/bindings_propertys_name_wl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file is a YAML whitelist 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
114 changes: 85 additions & 29 deletions scripts/ci/check_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import shutil
import textwrap
import unidiff
import yaml

from yamllint import config, linter

Expand All @@ -36,6 +37,28 @@
import list_boards
import list_hardware

sys.path.insert(0, os.path.join(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 not ZEPHYR_BASE:
ZEPHYR_BASE = str(Path(__file__).resolve().parents[2])
# Propagate this decision to child processes.
os.environ['ZEPHYR_BASE'] = ZEPHYR_BASE

# Initialize the propertys name whitelist
BINDINGS_PROPERTYS_WL = None
with open(Path(__file__).parent / 'bindings_propertys_name_wl.yaml', 'r') as f:
wl = yaml.safe_load(f.read())
if wl is not None:
BINDINGS_PROPERTYS_WL = set(wl)
else:
BINDINGS_PROPERTYS_WL = {'',}

logger = None

def git(*args, cwd=None, ignore_non_zero=False):
Expand Down Expand Up @@ -335,32 +358,75 @@ class DevicetreeBindingsCheck(ComplianceTest):
path_hint = "<zephyr-base>"

def run(self, full=True):
dts_bindings = self.parse_dt_bindings()

for dts_binding in dts_bindings:
self.required_false_check(dts_binding)
bindings_diff, bindings = self.get_yaml_bindings()

def parse_dt_bindings(self):
# If no bindings are changed, skip this check.
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')

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"):
if 'dts/bindings/' in file_name and file_name.endswith('.yaml'):
dt_bindings.append(file_name)
for file_name in get_files(filter='d'):
if BINDINGS_PATH in file_name:
p = file_name.partition(BINDINGS_PATH)
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):
with open(dts_binding) as file:
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")
bindings = edtlib.bindings_from_paths(bindings, ignore_errors=True)
return list(bindings_diff_dir), bindings

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_PROPERTYS_WL:
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_propertys_name_wl.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):
"""
Expand Down Expand Up @@ -1802,16 +1868,6 @@ def _main(args):
# The "real" main(), which is wrapped to catch exceptions and report them
# 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

# The absolute path of the top-level git directory. Initialize it here so
# that issues running Git can be reported to GitHub.
global GIT_TOP
Expand Down

0 comments on commit 0e899c5

Please sign in to comment.