1#!/usr/bin/env python3
2# SPDX-License-Identifier: Apache-2.0
3# Copyright (c) 2021 Intel Corporation
4
5import os
6import sh
7import argparse
8import re
9from unidiff import PatchSet
10
11if "ZEPHYR_BASE" not in os.environ:
12    exit("$ZEPHYR_BASE environment variable undefined.")
13
14repository_path = os.environ['ZEPHYR_BASE']
15
16sh_special_args = {
17    '_tty_out': False,
18    '_cwd': repository_path
19}
20
21coccinelle_scripts = ["/scripts/coccinelle/reserved_names.cocci",
22                      "/scripts/coccinelle/same_identifier.cocci",
23                      #"/scripts/coccinelle/identifier_length.cocci",
24                      ]
25
26
27def parse_coccinelle(contents: str, violations: dict):
28    reg = re.compile("([a-zA-Z0-9_/]*\\.[ch]:[0-9]*)(:[0-9\\-]*: )(.*)")
29    for line in contents.split("\n"):
30        r = reg.match(line)
31        if r:
32            f = r.group(1)
33            if f in violations:
34                violations[f].append(r.group(3))
35            else:
36                violations[r.group(1)] = [r.group(3)]
37
38
39def parse_args():
40    parser = argparse.ArgumentParser(
41        description="Check if change requires full twister")
42    parser.add_argument('-c', '--commits', default=None,
43                        help="Commit range in the form: a..b")
44    parser.add_argument("-o", "--output", required=False,
45                        help="Print violation into a file")
46    return parser.parse_args()
47
48
49def main():
50    args = parse_args()
51    if not args.commits:
52        exit("missing commit range")
53
54    # pylint does not like the 'sh' library
55    # pylint: disable=too-many-function-args,unexpected-keyword-arg
56    commit = sh.git("diff", args.commits, **sh_special_args)
57    patch_set = PatchSet(commit)
58    zephyr_base = os.getenv("ZEPHYR_BASE")
59    violations = {}
60    numViolations = 0
61
62    for f in patch_set:
63        if not f.path.endswith(".c") and not f.path.endswith(".h") or not os.path.exists(zephyr_base + "/" + f.path):
64            continue
65
66        for script in coccinelle_scripts:
67            script_path = os.getenv("ZEPHYR_BASE") + "/" + script
68            print(f"Running {script} on {f.path}")
69            try:
70                cocci = sh.coccicheck(
71                    "--mode=report",
72                    "--cocci=" +
73                    script_path,
74                    f.path,
75                    _timeout=10,
76                    **sh_special_args)
77                parse_coccinelle(cocci, violations)
78            except sh.TimeoutException:
79                print("we timed out waiting, skipping...")
80
81        for hunk in f:
82            for line in hunk:
83                if line.is_added:
84                    violation = "{}:{}".format(f.path, line.target_line_no)
85                    if violation in violations:
86                        numViolations += 1
87                        if args.output:
88                            with open(args.output, "a+") as fp:
89                                fp.write("{}:{}\n".format(
90                                    violation, "\t\n".join(
91                                        violations[violation])))
92                        else:
93                            print(
94                                "{}:{}".format(
95                                    violation, "\t\n".join(
96                                        violations[violation])))
97
98    return numViolations
99
100
101if __name__ == "__main__":
102    ret = main()
103    exit(ret)
104