xref: /openbmc/openbmc-build-scripts/tools/config-clang-tidy (revision b516729dd2dfbba714020095a2cc1104af069e09)
1*b516729dSPatrick Williams#!/usr/bin/env python3
2*b516729dSPatrick Williamsimport argparse
3*b516729dSPatrick Williamsimport os
4*b516729dSPatrick Williamsimport re
5*b516729dSPatrick Williamsfrom typing import Any, Dict, List, Tuple
6*b516729dSPatrick Williams
7*b516729dSPatrick Williamsimport yaml
8*b516729dSPatrick Williams
9*b516729dSPatrick Williams
10*b516729dSPatrick Williamsdef main() -> None:
11*b516729dSPatrick Williams    parser = argparse.ArgumentParser()
12*b516729dSPatrick Williams
13*b516729dSPatrick Williams    parser.add_argument("--repo", help="Path to the repository", default=".")
14*b516729dSPatrick Williams    parser.add_argument(
15*b516729dSPatrick Williams        "--commit",
16*b516729dSPatrick Williams        help="Commit the changes",
17*b516729dSPatrick Williams        default=False,
18*b516729dSPatrick Williams        action="store_true",
19*b516729dSPatrick Williams    )
20*b516729dSPatrick Williams
21*b516729dSPatrick Williams    subparsers = parser.add_subparsers()
22*b516729dSPatrick Williams    subparsers.required = True
23*b516729dSPatrick Williams
24*b516729dSPatrick Williams    parser_merge = subparsers.add_parser(
25*b516729dSPatrick Williams        "merge", help="Merge a reference clang-tidy config"
26*b516729dSPatrick Williams    )
27*b516729dSPatrick Williams    parser_merge.add_argument(
28*b516729dSPatrick Williams        "--reference", help="Path to reference clang-tidy", required=True
29*b516729dSPatrick Williams    )
30*b516729dSPatrick Williams    parser_merge.set_defaults(func=subcmd_merge)
31*b516729dSPatrick Williams
32*b516729dSPatrick Williams    parser_enable = subparsers.add_parser(
33*b516729dSPatrick Williams        "enable", help="Enable a rule in a reference clang-tidy config"
34*b516729dSPatrick Williams    )
35*b516729dSPatrick Williams    parser_enable.add_argument("check", help="Check to enable")
36*b516729dSPatrick Williams    parser_enable.set_defaults(func=subcmd_enable)
37*b516729dSPatrick Williams
38*b516729dSPatrick Williams    parser_disable = subparsers.add_parser(
39*b516729dSPatrick Williams        "disable", help="Enable a rule in a reference clang-tidy config"
40*b516729dSPatrick Williams    )
41*b516729dSPatrick Williams    parser_disable.add_argument("check", help="Check to disable")
42*b516729dSPatrick Williams    parser_disable.add_argument(
43*b516729dSPatrick Williams        "--drop", help="Delete the check from the config", action="store_true"
44*b516729dSPatrick Williams    )
45*b516729dSPatrick Williams    parser_disable.set_defaults(func=subcmd_disable)
46*b516729dSPatrick Williams
47*b516729dSPatrick Williams    args = parser.parse_args()
48*b516729dSPatrick Williams    args.func(args)
49*b516729dSPatrick Williams
50*b516729dSPatrick Williams
51*b516729dSPatrick Williamsdef subcmd_merge(args: argparse.Namespace) -> None:
52*b516729dSPatrick Williams    repo_path, repo_config = load_config(args.repo)
53*b516729dSPatrick Williams    ref_path, ref_config = load_config(args.reference)
54*b516729dSPatrick Williams
55*b516729dSPatrick Williams    result = {}
56*b516729dSPatrick Williams
57*b516729dSPatrick Williams    all_keys_set = set(repo_config.keys()) | set(ref_config.keys())
58*b516729dSPatrick Williams    special_keys = ["Checks", "CheckOptions"]
59*b516729dSPatrick Williams
60*b516729dSPatrick Williams    # Create ordered_keys: special keys first (if present, in their defined order),
61*b516729dSPatrick Williams    # followed by the rest of the keys sorted alphabetically.
62*b516729dSPatrick Williams    ordered_keys = [k for k in special_keys if k in all_keys_set] + sorted(
63*b516729dSPatrick Williams        list(all_keys_set - set(special_keys))
64*b516729dSPatrick Williams    )
65*b516729dSPatrick Williams
66*b516729dSPatrick Williams    for key in ordered_keys:
67*b516729dSPatrick Williams        repo_value = repo_config.get(key)
68*b516729dSPatrick Williams        ref_value = ref_config.get(key)
69*b516729dSPatrick Williams
70*b516729dSPatrick Williams        key_class = globals().get(f"Key_{key}")
71*b516729dSPatrick Williams        if key_class and hasattr(key_class, "merge"):
72*b516729dSPatrick Williams            result[key] = key_class.merge(repo_value, ref_value)
73*b516729dSPatrick Williams        elif repo_value:
74*b516729dSPatrick Williams            result[key] = repo_value
75*b516729dSPatrick Williams        else:
76*b516729dSPatrick Williams            result[key] = ref_value
77*b516729dSPatrick Williams
78*b516729dSPatrick Williams    with open(repo_path, "w") as f:
79*b516729dSPatrick Williams        f.write(format_yaml_output(result))
80*b516729dSPatrick Williams
81*b516729dSPatrick Williams
82*b516729dSPatrick Williamsdef subcmd_enable(args: argparse.Namespace) -> None:
83*b516729dSPatrick Williams    repo_path, repo_config = load_config(args.repo)
84*b516729dSPatrick Williams
85*b516729dSPatrick Williams    if "Checks" in repo_config:
86*b516729dSPatrick Williams        repo_config["Checks"] = Key_Checks.enable(
87*b516729dSPatrick Williams            repo_config["Checks"], args.check
88*b516729dSPatrick Williams        )
89*b516729dSPatrick Williams
90*b516729dSPatrick Williams    with open(repo_path, "w") as f:
91*b516729dSPatrick Williams        f.write(format_yaml_output(repo_config))
92*b516729dSPatrick Williams
93*b516729dSPatrick Williams    pass
94*b516729dSPatrick Williams
95*b516729dSPatrick Williams
96*b516729dSPatrick Williamsdef subcmd_disable(args: argparse.Namespace) -> None:
97*b516729dSPatrick Williams    repo_path, repo_config = load_config(args.repo)
98*b516729dSPatrick Williams
99*b516729dSPatrick Williams    if "Checks" in repo_config:
100*b516729dSPatrick Williams        repo_config["Checks"] = Key_Checks.disable(
101*b516729dSPatrick Williams            repo_config["Checks"], args.check, args.drop
102*b516729dSPatrick Williams        )
103*b516729dSPatrick Williams
104*b516729dSPatrick Williams    with open(repo_path, "w") as f:
105*b516729dSPatrick Williams        f.write(format_yaml_output(repo_config))
106*b516729dSPatrick Williams
107*b516729dSPatrick Williams    pass
108*b516729dSPatrick Williams
109*b516729dSPatrick Williams
110*b516729dSPatrick Williamsclass Key_Checks:
111*b516729dSPatrick Williams    @staticmethod
112*b516729dSPatrick Williams    def merge(repo: str, ref: str) -> str:
113*b516729dSPatrick Williams        repo_checks = Key_Checks._split(repo)
114*b516729dSPatrick Williams        ref_checks = Key_Checks._split(ref)
115*b516729dSPatrick Williams
116*b516729dSPatrick Williams        result: Dict[str, bool] = {}
117*b516729dSPatrick Williams
118*b516729dSPatrick Williams        for k, v in repo_checks.items():
119*b516729dSPatrick Williams            result[k] = v
120*b516729dSPatrick Williams        for k, v in ref_checks.items():
121*b516729dSPatrick Williams            if k not in result:
122*b516729dSPatrick Williams                result[k] = False
123*b516729dSPatrick Williams
124*b516729dSPatrick Williams        return Key_Checks._join(result)
125*b516729dSPatrick Williams
126*b516729dSPatrick Williams    @staticmethod
127*b516729dSPatrick Williams    def enable(repo: str, check: str) -> str:
128*b516729dSPatrick Williams        repo_checks = Key_Checks._split(repo)
129*b516729dSPatrick Williams        repo_checks[check] = True
130*b516729dSPatrick Williams        return Key_Checks._join(repo_checks)
131*b516729dSPatrick Williams
132*b516729dSPatrick Williams    @staticmethod
133*b516729dSPatrick Williams    def disable(repo: str, check: str, drop: bool) -> str:
134*b516729dSPatrick Williams        repo_checks = Key_Checks._split(repo)
135*b516729dSPatrick Williams        if drop:
136*b516729dSPatrick Williams            repo_checks.pop(check, None)
137*b516729dSPatrick Williams        else:
138*b516729dSPatrick Williams            repo_checks[check] = False
139*b516729dSPatrick Williams        return Key_Checks._join(repo_checks)
140*b516729dSPatrick Williams
141*b516729dSPatrick Williams    @staticmethod
142*b516729dSPatrick Williams    def _split(s: str) -> Dict[str, bool]:
143*b516729dSPatrick Williams        result: Dict[str, bool] = {}
144*b516729dSPatrick Williams        if not s:
145*b516729dSPatrick Williams            return result
146*b516729dSPatrick Williams        for item in s.split():
147*b516729dSPatrick Williams            item = item.replace(",", "")
148*b516729dSPatrick Williams            if "-*" in item:
149*b516729dSPatrick Williams                continue
150*b516729dSPatrick Williams            if item.startswith("-"):
151*b516729dSPatrick Williams                result[item[1:]] = False
152*b516729dSPatrick Williams            else:
153*b516729dSPatrick Williams                result[item] = True
154*b516729dSPatrick Williams        return result
155*b516729dSPatrick Williams
156*b516729dSPatrick Williams    @staticmethod
157*b516729dSPatrick Williams    def _join(data: Dict[str, bool]) -> str:
158*b516729dSPatrick Williams        return (
159*b516729dSPatrick Williams            ",\n".join(
160*b516729dSPatrick Williams                ["-*"] + [k if v else f"-{k}" for k, v in sorted(data.items())]
161*b516729dSPatrick Williams            )
162*b516729dSPatrick Williams            + "\n"
163*b516729dSPatrick Williams        )
164*b516729dSPatrick Williams
165*b516729dSPatrick Williams
166*b516729dSPatrick Williamsclass Key_CheckOptions:
167*b516729dSPatrick Williams    @staticmethod
168*b516729dSPatrick Williams    def merge(
169*b516729dSPatrick Williams        repo: List[Dict[str, str]], ref: List[Dict[str, str]]
170*b516729dSPatrick Williams    ) -> List[Dict[str, str]]:
171*b516729dSPatrick Williams        unrolled_repo: Dict[str, str] = {}
172*b516729dSPatrick Williams        for item in repo or []:
173*b516729dSPatrick Williams            unrolled_repo[item["key"]] = item["value"]
174*b516729dSPatrick Williams        for item in ref or []:
175*b516729dSPatrick Williams            if item["key"] in unrolled_repo:
176*b516729dSPatrick Williams                continue
177*b516729dSPatrick Williams            unrolled_repo[item["key"]] = item["value"]
178*b516729dSPatrick Williams
179*b516729dSPatrick Williams        return [
180*b516729dSPatrick Williams            {"key": k, "value": v} for k, v in sorted(unrolled_repo.items())
181*b516729dSPatrick Williams        ]
182*b516729dSPatrick Williams
183*b516729dSPatrick Williams
184*b516729dSPatrick Williamsdef load_config(path: str) -> Tuple[str, Dict[str, Any]]:
185*b516729dSPatrick Williams    if "clang-tidy" not in path:
186*b516729dSPatrick Williams        path = os.path.join(path, ".clang-tidy")
187*b516729dSPatrick Williams
188*b516729dSPatrick Williams    if not os.path.exists(path):
189*b516729dSPatrick Williams        return (path, {})
190*b516729dSPatrick Williams
191*b516729dSPatrick Williams    with open(path, "r") as f:
192*b516729dSPatrick Williams        return (path, yaml.safe_load(f))
193*b516729dSPatrick Williams
194*b516729dSPatrick Williams
195*b516729dSPatrick Williamsdef format_yaml_output(data: Dict[str, Any]) -> str:
196*b516729dSPatrick Williams    """Convert to a prettier YAML string:
197*b516729dSPatrick Williams    - filter out excess empty lines
198*b516729dSPatrick Williams    - insert new lines between keys
199*b516729dSPatrick Williams    """
200*b516729dSPatrick Williams    yaml_string = yaml.dump(data, sort_keys=False, indent=4)
201*b516729dSPatrick Williams    lines: List[str] = []
202*b516729dSPatrick Williams    for line in yaml_string.split("\n"):
203*b516729dSPatrick Williams        # Strip excess new lines.
204*b516729dSPatrick Williams        if not line:
205*b516729dSPatrick Williams            continue
206*b516729dSPatrick Williams        # Add new line between keys.
207*b516729dSPatrick Williams        if len(lines) and re.match("[a-zA-Z0-9]+:", line):
208*b516729dSPatrick Williams            lines.append("")
209*b516729dSPatrick Williams        lines.append(line)
210*b516729dSPatrick Williams    lines.append("")
211*b516729dSPatrick Williams
212*b516729dSPatrick Williams    return "\n".join(lines)
213*b516729dSPatrick Williams
214*b516729dSPatrick Williams
215*b516729dSPatrick Williamsif __name__ == "__main__":
216*b516729dSPatrick Williams    main()
217