| #!/usr/bin/env python3 |
| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright (c) 2021 Intel Corporation |
| |
| import argparse |
| import os |
| import re |
| |
| import sh |
| from unidiff import PatchSet |
| |
| if "ZEPHYR_BASE" not in os.environ: |
| exit("$ZEPHYR_BASE environment variable undefined.") |
| |
| RESERVED_NAMES_SCRIPT = "/scripts/coccinelle/reserved_names.cocci" |
| |
| coccinelle_scripts = [ |
| RESERVED_NAMES_SCRIPT, |
| "/scripts/coccinelle/same_identifier.cocci", |
| # "/scripts/coccinelle/identifier_length.cocci", |
| ] |
| |
| coccinelle_reserved_names_exclude_regex = [ |
| r"lib/libc/.*", |
| r"lib/posix/.*", |
| r"include/zephyr/posix/.*", |
| ] |
| |
| |
| def parse_coccinelle(contents: str, violations: dict): |
| reg = re.compile("([a-zA-Z0-9_/]*\\.[ch]:[0-9]*)(:[0-9\\-]*: )(.*)") |
| for line in contents.split("\n"): |
| r = reg.match(line) |
| if r: |
| f = r.group(1) |
| if f in violations: |
| violations[f].append(r.group(3)) |
| else: |
| violations[r.group(1)] = [r.group(3)] |
| |
| |
| def parse_args(): |
| parser = argparse.ArgumentParser( |
| description="Check commits against Cocccinelle rules", allow_abbrev=False |
| ) |
| parser.add_argument('-r', "--repository", required=False, help="Path to repository") |
| parser.add_argument('-c', '--commits', default=None, help="Commit range in the form: a..b") |
| parser.add_argument("-o", "--output", required=False, help="Print violation into a file") |
| return parser.parse_args() |
| |
| |
| def main(): |
| args = parse_args() |
| if not args.commits: |
| exit("missing commit range") |
| |
| if args.repository is None: |
| repository_path = os.environ['ZEPHYR_BASE'] |
| else: |
| repository_path = args.repository |
| |
| sh_special_args = {'_tty_out': False, '_cwd': repository_path} |
| |
| # pylint does not like the 'sh' library |
| # pylint: disable=too-many-function-args,unexpected-keyword-arg |
| commit = sh.git("diff", args.commits, **sh_special_args) |
| patch_set = PatchSet(commit) |
| zephyr_base = os.getenv("ZEPHYR_BASE") |
| violations = {} |
| numViolations = 0 |
| |
| for f in patch_set: |
| c_file = f.path.endswith(".c") |
| h_file = f.path.endswith(".h") |
| exists = os.path.exists(zephyr_base + "/" + f.path) |
| if not c_file and not h_file or not exists: |
| continue |
| |
| for script in coccinelle_scripts: |
| skip_reserved_names = False |
| if script == RESERVED_NAMES_SCRIPT: |
| for path in coccinelle_reserved_names_exclude_regex: |
| if re.match(path, f.path): |
| skip_reserved_names = True |
| break |
| |
| if skip_reserved_names: |
| continue |
| |
| script_path = zephyr_base + "/" + script |
| print(f"Running {script} on {f.path}") |
| try: |
| cocci = sh.coccicheck( |
| "--mode=report", |
| "--cocci=" + script_path, |
| f.path, |
| _timeout=10, |
| **sh_special_args, |
| ) |
| parse_coccinelle(cocci, violations) |
| except sh.TimeoutException: |
| print("we timed out waiting, skipping...") |
| |
| for hunk in f: |
| for line in hunk: |
| if line.is_added: |
| violation = f"{f.path}:{line.target_line_no}" |
| if violation in violations: |
| v_str = "\t\n".join(violations[violation]) |
| out_str = f"{violation}:{v_str}" |
| numViolations += 1 |
| if args.output: |
| with open(args.output, "a+") as fp: |
| fp.write(f"{out_str}\n") |
| else: |
| print(out_str) |
| |
| return numViolations |
| |
| |
| if __name__ == "__main__": |
| ret = main() |
| exit(ret) |