|
21 | 21 | import shutil
|
22 | 22 | import textwrap
|
23 | 23 | import unidiff
|
| 24 | +import yaml |
24 | 25 |
|
25 | 26 | from yamllint import config, linter
|
26 | 27 |
|
|
35 | 36 | import list_boards
|
36 | 37 | import list_hardware
|
37 | 38 |
|
| 39 | +sys.path.insert(0, str(Path(__file__).resolve().parents[2] |
| 40 | + / "scripts" / "dts" / "python-devicetree" / "src")) |
| 41 | +from devicetree import edtlib |
| 42 | + |
| 43 | + |
| 44 | +# Let the user run this script as ./scripts/ci/check_compliance.py without |
| 45 | +# making them set ZEPHYR_BASE. |
| 46 | +ZEPHYR_BASE = Path(os.environ.get('ZEPHYR_BASE')) |
| 47 | +if not ZEPHYR_BASE: |
| 48 | + ZEPHYR_BASE = Path(__file__).resolve().parents[2] |
| 49 | + # Propagate this decision to child processes. |
| 50 | + os.environ['ZEPHYR_BASE'] = str(ZEPHYR_BASE) |
| 51 | + |
| 52 | +# Initialize the property names allowlist |
| 53 | +BINDINGS_PROPERTIES_AL = None |
| 54 | +with open(Path(__file__).parents[1] / 'bindings_properties_allowlist.yaml') as f: |
| 55 | + allowlist = yaml.safe_load(f.read()) |
| 56 | + if allowlist is not None: |
| 57 | + BINDINGS_PROPERTIES_AL = set(allowlist) |
| 58 | + else: |
| 59 | + BINDINGS_PROPERTIES_AL = set() |
| 60 | + |
38 | 61 | logger = None
|
39 | 62 |
|
40 | 63 | def git(*args, cwd=None, ignore_non_zero=False):
|
@@ -318,7 +341,7 @@ def run(self):
|
318 | 341 |
|
319 | 342 | vendor_prefixes = {"others"}
|
320 | 343 | # add vendor prefixes from the main zephyr repo
|
321 |
| - vendor_prefixes |= get_vendor_prefixes(ZEPHYR_BASE / "dts" / "bindings" / "vendor-prefixes.txt", self.error) |
| 344 | + vendor_prefixes |= get_vendor_prefixes(str(ZEPHYR_BASE) , self.error) |
322 | 345 |
|
323 | 346 | # add vendor prefixes from the current repo
|
324 | 347 | dts_roots = get_module_setting_root('dts', path / "zephyr" / "module.yml")
|
@@ -380,32 +403,75 @@ class DevicetreeBindingsCheck(ComplianceTest):
|
380 | 403 | doc = "See https://docs.zephyrproject.org/latest/build/dts/bindings.html for more details."
|
381 | 404 |
|
382 | 405 | def run(self, full=True):
|
383 |
| - dts_bindings = self.parse_dt_bindings() |
384 |
| - |
385 |
| - for dts_binding in dts_bindings: |
386 |
| - self.required_false_check(dts_binding) |
| 406 | + bindings_diff, bindings = self.get_yaml_bindings() |
387 | 407 |
|
388 |
| - def parse_dt_bindings(self): |
| 408 | + # If no bindings are changed, skip this check. |
| 409 | + try: |
| 410 | + subprocess.check_call(['git', 'diff', '--quiet', COMMIT_RANGE] |
| 411 | + + bindings_diff) |
| 412 | + nodiff = True |
| 413 | + except subprocess.CalledProcessError: |
| 414 | + nodiff = False |
| 415 | + if nodiff: |
| 416 | + self.skip('no changes to bindings were made') |
| 417 | + |
| 418 | + for binding in bindings: |
| 419 | + self.check(binding, self.check_yaml_property_name) |
| 420 | + self.check(binding, self.required_false_check) |
| 421 | + |
| 422 | + @staticmethod |
| 423 | + def check(binding, callback): |
| 424 | + while binding is not None: |
| 425 | + callback(binding) |
| 426 | + binding = binding.child_binding |
| 427 | + |
| 428 | + def get_yaml_bindings(self): |
389 | 429 | """
|
390 |
| - Returns a list of dts/bindings/**/*.yaml files |
| 430 | + Returns a list of 'dts/bindings/**/*.yaml' |
391 | 431 | """
|
| 432 | + from glob import glob |
| 433 | + BINDINGS_PATH = 'dts/bindings/' |
| 434 | + bindings_diff_dir, bindings = set(), [] |
392 | 435 |
|
393 |
| - dt_bindings = [] |
394 |
| - for file_name in get_files(filter="d"): |
395 |
| - if 'dts/bindings/' in file_name and file_name.endswith('.yaml'): |
396 |
| - dt_bindings.append(file_name) |
| 436 | + for file_name in get_files(filter='d'): |
| 437 | + if BINDINGS_PATH in file_name: |
| 438 | + p = file_name.partition(BINDINGS_PATH) |
| 439 | + bindings_diff_dir.add(os.path.join(p[0], p[1])) |
397 | 440 |
|
398 |
| - return dt_bindings |
| 441 | + for path in bindings_diff_dir: |
| 442 | + yamls = glob(f'{os.fspath(path)}/**/*.yaml', recursive=True) |
| 443 | + bindings.extend(yamls) |
399 | 444 |
|
400 |
| - def required_false_check(self, dts_binding): |
401 |
| - with open(dts_binding) as file: |
402 |
| - for line_number, line in enumerate(file, 1): |
403 |
| - if 'required: false' in line: |
404 |
| - self.fmtd_failure( |
405 |
| - 'warning', 'Devicetree Bindings', dts_binding, |
406 |
| - line_number, col=None, |
407 |
| - desc="'required: false' is redundant, please remove") |
| 445 | + bindings = edtlib.bindings_from_paths(bindings, ignore_errors=True) |
| 446 | + return list(bindings_diff_dir), bindings |
| 447 | + |
| 448 | + def check_yaml_property_name(self, binding): |
| 449 | + """ |
| 450 | + Checks if the property names in the binding file contain underscores. |
| 451 | + """ |
| 452 | + for prop_name in binding.prop2specs: |
| 453 | + if '_' in prop_name and prop_name not in BINDINGS_PROPERTIES_AL: |
| 454 | + better_prop = prop_name.replace('_', '-') |
| 455 | + print(f"Required: In '{binding.path}', " |
| 456 | + f"the property '{prop_name}' " |
| 457 | + f"should be renamed to '{better_prop}'.") |
| 458 | + self.failure( |
| 459 | + f"{binding.path}: property '{prop_name}' contains underscores.\n" |
| 460 | + f"\tUse '{better_prop}' instead unless this property name is from Linux.\n" |
| 461 | + "Or another authoritative upstream source of bindings for " |
| 462 | + f"compatible '{binding.compatible}'.\n" |
| 463 | + "\tHint: update 'bindings_properties_allowlist.yaml' if you need to " |
| 464 | + "override this check for this property." |
| 465 | + ) |
408 | 466 |
|
| 467 | + def required_false_check(self, binding): |
| 468 | + raw_props = binding.raw.get('properties', {}) |
| 469 | + for prop_name, raw_prop in raw_props.items(): |
| 470 | + if raw_prop.get('required') is False: |
| 471 | + self.failure( |
| 472 | + f'{binding.path}: property "{prop_name}": ' |
| 473 | + "'required: false' is redundant, please remove" |
| 474 | + ) |
409 | 475 |
|
410 | 476 | class KconfigCheck(ComplianceTest):
|
411 | 477 | """
|
@@ -1996,17 +2062,6 @@ def _main(args):
|
1996 | 2062 | # The "real" main(), which is wrapped to catch exceptions and report them
|
1997 | 2063 | # to GitHub. Returns the number of test failures.
|
1998 | 2064 |
|
1999 |
| - global ZEPHYR_BASE |
2000 |
| - ZEPHYR_BASE = os.environ.get('ZEPHYR_BASE') |
2001 |
| - if not ZEPHYR_BASE: |
2002 |
| - # Let the user run this script as ./scripts/ci/check_compliance.py without |
2003 |
| - # making them set ZEPHYR_BASE. |
2004 |
| - ZEPHYR_BASE = str(Path(__file__).resolve().parents[2]) |
2005 |
| - |
2006 |
| - # Propagate this decision to child processes. |
2007 |
| - os.environ['ZEPHYR_BASE'] = ZEPHYR_BASE |
2008 |
| - ZEPHYR_BASE = Path(ZEPHYR_BASE) |
2009 |
| - |
2010 | 2065 | # The absolute path of the top-level git directory. Initialize it here so
|
2011 | 2066 | # that issues running Git can be reported to GitHub.
|
2012 | 2067 | global GIT_TOP
|
|
0 commit comments