xref: /qemu/scripts/rust/rustc_args.py (revision 97ed1e9c8e2b0744508ef61cac8a23bb1e107820)
1#!/usr/bin/env python3
2
3"""Generate rustc arguments for meson rust builds.
4
5This program generates --cfg compile flags for the configuration headers passed
6as arguments.
7
8Copyright (c) 2024 Linaro Ltd.
9
10Authors:
11 Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
12
13This program is free software; you can redistribute it and/or modify
14it under the terms of the GNU General Public License as published by
15the Free Software Foundation; either version 2 of the License, or
16(at your option) any later version.
17
18This program is distributed in the hope that it will be useful,
19but WITHOUT ANY WARRANTY; without even the implied warranty of
20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License
24along with this program.  If not, see <http://www.gnu.org/licenses/>.
25"""
26
27import argparse
28from dataclasses import dataclass
29import logging
30from pathlib import Path
31from typing import Any, Iterable, List, Mapping, Optional, Set
32
33try:
34    import tomllib
35except ImportError:
36    import tomli as tomllib
37
38
39class CargoTOML:
40    tomldata: Mapping[Any, Any]
41    check_cfg: Set[str]
42
43    def __init__(self, path: str):
44        with open(path, 'rb') as f:
45            self.tomldata = tomllib.load(f)
46
47        self.check_cfg = set(self.find_check_cfg())
48
49    def find_check_cfg(self) -> Iterable[str]:
50        toml_lints = self.lints
51        rust_lints = toml_lints.get("rust", {})
52        cfg_lint = rust_lints.get("unexpected_cfgs", {})
53        return cfg_lint.get("check-cfg", [])
54
55    @property
56    def lints(self) -> Mapping[Any, Any]:
57        return self.get_table("lints")
58
59    def get_table(self, key: str) -> Mapping[Any, Any]:
60        table = self.tomldata.get(key, {})
61
62        return table
63
64
65@dataclass
66class LintFlag:
67    flags: List[str]
68    priority: int
69
70
71def generate_lint_flags(cargo_toml: CargoTOML) -> Iterable[str]:
72    """Converts Cargo.toml lints to rustc -A/-D/-F/-W flags."""
73
74    toml_lints = cargo_toml.lints
75
76    lint_list = []
77    for k, v in toml_lints.items():
78        prefix = "" if k == "rust" else k + "::"
79        for lint, data in v.items():
80            level = data if isinstance(data, str) else data["level"]
81            priority = 0 if isinstance(data, str) else data.get("priority", 0)
82            if level == "deny":
83                flag = "-D"
84            elif level == "allow":
85                flag = "-A"
86            elif level == "warn":
87                flag = "-W"
88            elif level == "forbid":
89                flag = "-F"
90            else:
91                raise Exception(f"invalid level {level} for {prefix}{lint}")
92
93            # This may change if QEMU ever invokes clippy-driver or rustdoc by
94            # hand.  For now, check the syntax but do not add non-rustc lints to
95            # the command line.
96            if k == "rust":
97                lint_list.append(LintFlag(flags=[flag, prefix + lint], priority=priority))
98
99    lint_list.sort(key=lambda x: x.priority)
100    for lint in lint_list:
101        yield from lint.flags
102
103
104def generate_cfg_flags(header: str, cargo_toml: CargoTOML) -> Iterable[str]:
105    """Converts defines from config[..].h headers to rustc --cfg flags."""
106
107    with open(header, encoding="utf-8") as cfg:
108        config = [l.split()[1:] for l in cfg if l.startswith("#define")]
109
110    cfg_list = []
111    for cfg in config:
112        name = cfg[0]
113        if f'cfg({name})' not in cargo_toml.check_cfg:
114            continue
115        if len(cfg) >= 2 and cfg[1] != "1":
116            continue
117        cfg_list.append("--cfg")
118        cfg_list.append(name)
119    return cfg_list
120
121
122def main() -> None:
123    parser = argparse.ArgumentParser()
124    parser.add_argument("-v", "--verbose", action="store_true")
125    parser.add_argument(
126        "--config-headers",
127        metavar="CONFIG_HEADER",
128        action="append",
129        dest="config_headers",
130        help="paths to any configuration C headers (*.h files), if any",
131        required=False,
132        default=[],
133    )
134    parser.add_argument(
135        metavar="TOML_FILE",
136        action="store",
137        dest="cargo_toml",
138        help="path to Cargo.toml file",
139    )
140    parser.add_argument(
141        "--features",
142        action="store_true",
143        dest="features",
144        help="generate --check-cfg arguments for features",
145        required=False,
146        default=None,
147    )
148    parser.add_argument(
149        "--lints",
150        action="store_true",
151        dest="lints",
152        help="generate arguments from [lints] table",
153        required=False,
154        default=None,
155    )
156    parser.add_argument(
157        "--rustc-version",
158        metavar="VERSION",
159        dest="rustc_version",
160        action="store",
161        help="version of rustc",
162        required=False,
163        default="1.0.0",
164    )
165    args = parser.parse_args()
166    if args.verbose:
167        logging.basicConfig(level=logging.DEBUG)
168    logging.debug("args: %s", args)
169
170    rustc_version = tuple((int(x) for x in args.rustc_version.split('.')[0:2]))
171    cargo_toml = CargoTOML(args.cargo_toml)
172
173    if args.lints:
174        for tok in generate_lint_flags(cargo_toml):
175            print(tok)
176
177    if rustc_version >= (1, 80):
178        if args.lints:
179            for cfg in sorted(cargo_toml.check_cfg):
180                print("--check-cfg")
181                print(cfg)
182        if args.features:
183            for feature in cargo_toml.get_table("features"):
184                if feature != "default":
185                    print("--check-cfg")
186                    print(f'cfg(feature,values("{feature}"))')
187
188    for header in args.config_headers:
189        for tok in generate_cfg_flags(header, cargo_toml):
190            print(tok)
191
192
193if __name__ == "__main__":
194    main()
195